diff --git a/.prettierrc b/.prettierrc
index e53e300..bad3a70 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -6,5 +6,5 @@
"jsxSingleQuote": false,
"trailingComma": "none",
"bracketSameLine": false,
- "bracketSpacing": false
+ "bracketSpacing": true
}
diff --git a/README.md b/README.md
index 879c41e..bdf249b 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,13 @@
# Chrome Extension (MV3) Boilerplate with React 18 and Vite 5
-[//]: # 'TODO: Publish the template on NPM'
-[//]: # '[![npm](https://img.shields.io/npm/v/chrome-extension-boilerplate-react)](https://www.npmjs.com/package/chrome-extension-boilerplate-react)'
-[//]: # '[![npm-download](https://img.shields.io/npm/dw/chrome-extension-boilerplate-react)](https://www.npmjs.com/package/chrome-extension-boilerplate-react)'
-[//]: # '[![npm](https://img.shields.io/npm/dm/chrome-extension-boilerplate-react)](https://www.npmjs.com/package/chrome-extension-boilerplate-react)'
+[//]: # (# 'TODO: Publish the template on NPM')
+
+[//]: # ([![npm](https://img.shields.io/npm/v/chrome-extension-boilerplate-react)](https://www.npmjs.com/package/chrome-extension-boilerplate-react))
+
+[//]: # ([![npm-download](https://img.shields.io/npm/dw/chrome-extension-boilerplate-react)](https://www.npmjs.com/package/chrome-extension-boilerplate-react))
+
+[//]: # ([![npm](https://img.shields.io/npm/dm/chrome-extension-boilerplate-react)](https://www.npmjs.com/package/chrome-extension-boilerplate-react))
This repository contains a boilerplate for building Chrome Extensions with React 18, TypeScript, and Vite 5.
This boilerplate is inspired by and adapted
@@ -14,21 +17,19 @@ from [chrome-extension-boilerplate-react](https://github.com/lxieyang/chrome-ext
## Features
This is a basic Chrome Extensions boilerplate to help you write modular and modern Javascript code and load CSS easily.
-This boilerplate is updated with:
-
-- [Chrome Extension Manifest V3](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/)
-- [React 18](https://reactjs.org)
-- [MUI](https://mui.com/)
-- ESLint:
- - [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react)
- - [eslint-config-prettier](https://www.npmjs.com/package/eslint-config-prettier)
- - [eslint-plugin-simple-import-sort](https://www.npmjs.com/package/eslint-plugin-simple-import-sort)
- - [typescript-eslint](https://www.npmjs.com/package/typescript-eslint)
-- [Prettier](https://prettier.io/)
-- [TypeScript](https://www.typescriptlang.org/)
-
-I have avoided using CRXJS Vite Plugin on purpose as it's last update was in 2022, and it could possibly have some
-issues with newer versions of Vite.
+This boilerplate is using:
+
+- [Chrome Extension Manifest V3](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/)
+- [React 18](https://reactjs.org)
+- [MUI](https://mui.com/)
+- [CRXJS Vite Plugin](https://www.npmjs.com/package/@crxjs/vite-plugin/v/2.0.0-beta.23)
+- ESLint:
+ - [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react)
+ - [eslint-config-prettier](https://www.npmjs.com/package/eslint-config-prettier)
+ - [eslint-plugin-simple-import-sort](https://www.npmjs.com/package/eslint-plugin-simple-import-sort)
+ - [typescript-eslint](https://www.npmjs.com/package/typescript-eslint)
+- [Prettier](https://prettier.io/)
+- [TypeScript](https://www.typescriptlang.org/)
Please open up an issue to nudge me to keep the npm packages up-to-date.
@@ -39,9 +40,9 @@ Please open up an issue to nudge me to keep the npm packages up-to-date.
1. Check if your [Node.js](https://nodejs.org/) version is >= **18**.
2. Clone this repository.
3. Change the package's `name`, `description`, and `repository` fields in `package.json`.
-4. Change the name of your extension on `src/manifest.json`.
+4. Change the name of your extension in `manifest.json`.
5. Run `yarn install` to install the dependencies.
-6. Run `yarn dist`
+6. Run `yarn build`
7. Load your extension on Chrome following:
1. Access `chrome://extensions/`
2. Turn the `Developer mode` switch on (top right corner)
@@ -64,16 +65,14 @@ This boilerplate supports TypeScript! Everything that can be written in TypeScri
## Change Watchers
-This boilerplate has watch scripts for the popup (`yarn watch:popup`), background script (`yarn watch:background`), and
-content script (`yarn watch:content`).
-`yarn dist` has to be run first to copy assets, `manifest.json` and Chrome extension files to the `dist` folder.
+This boilerplate has a watch script (`yarn watch`) which will update the extension's code every time you save a file.
## Packing
After the development of your extension run the command
```
-$ yarn dist
+$ yarn build
```
Now, the content of `dist` folder will be the extension ready to be submitted to the Chrome Web Store. Just take a look
@@ -81,7 +80,7 @@ at the [official guide](https://developer.chrome.com/webstore/publish) to more i
## Resources:
-- [Chrome Extension documentation](https://developer.chrome.com/extensions/getstarted)
+- [Chrome Extension documentation](https://developer.chrome.com/extensions/getstarted)
---
diff --git a/eslint.config.js b/eslint.config.js
index 553d438..09be896 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -15,7 +15,6 @@ export default tseslint.config(
...tseslint.configs.recommended,
reactRecommended,
reactJsxRuntime,
- eslintConfigPrettier,
{
plugins: {
'simple-import-sort': simpleImportSort
@@ -24,5 +23,6 @@ export default tseslint.config(
'simple-import-sort/imports': 'warn',
'simple-import-sort/exports': 'warn'
}
- }
+ },
+ eslintConfigPrettier
);
diff --git a/manifest.json b/manifest.json
index c95f36b..2402f42 100644
--- a/manifest.json
+++ b/manifest.json
@@ -9,17 +9,17 @@
},
"action": {
"default_title": "Chrome Extension React",
- "default_popup": "popup/index.html",
+ "default_popup": "src/popup/index.html",
"default_icon": "assets/icons/icon-34.png"
},
"content_scripts": [
{
- "js": ["content/index.js"],
+ "js": ["src/content/index.ts"],
"matches": ["*://*/*"]
}
],
"background": {
- "service_worker": "background/index.js"
+ "service_worker": "src/background/index.ts"
},
"web_accessible_resources": [
{
diff --git a/package.json b/package.json
index 1995706..20be6d4 100644
--- a/package.json
+++ b/package.json
@@ -8,18 +8,8 @@
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
- "watch": "yarn dist && yarn watch:popup",
- "watch:background": "vite --config vite.config.background.ts build --watch",
- "watch:content": "vite --config vite.config.content.ts build --watch",
- "watch:popup": "vite --config vite.config.popup.ts build --watch",
- "build": "yarn build:background && yarn build:content && yarn build:popup",
- "build:background": "vite --config vite.config.background.ts build",
- "build:content": "vite --config vite.config.content.ts build",
- "build:popup": "vite --config vite.config.popup.ts build",
- "clean": "rimraf dist",
- "copy:static": "copyfiles manifest.json assets/**/* dist",
- "dist:dev": "yarn lint && yarn dist",
- "dist": "yarn clean && yarn build && yarn copy:static"
+ "watch": "vite build --watch",
+ "build": "vite build"
},
"dependencies": {
"@emotion/react": "^11.11.4",
@@ -34,6 +24,7 @@
"react-router-dom": "^6.22.3"
},
"devDependencies": {
+ "@crxjs/vite-plugin": "2.0.0-beta.23",
"@eslint/js": "^8.57.0",
"@types/react": "^18.2.69",
"@types/react-dom": "^18.2.22",
diff --git a/src/common/chrome-api-wrapper.ts b/src/common/chrome-api-wrapper.ts
index 2b38592..815203b 100644
--- a/src/common/chrome-api-wrapper.ts
+++ b/src/common/chrome-api-wrapper.ts
@@ -28,7 +28,7 @@ export class ChromeApiWrapper {
// Prevents code from failing when used too soon
while (Date.now() - dateStarted < 5000) {
- const tabs = await chrome.tabs.query({active: true, lastFocusedWindow: true});
+ const tabs = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
const currentTab = tabs[0];
if (currentTab?.status === 'complete') {
diff --git a/src/content/index.ts b/src/content/index.ts
index a2f63d9..b893099 100644
--- a/src/content/index.ts
+++ b/src/content/index.ts
@@ -1,5 +1,5 @@
-import {ChromeMessage, ChromeMessageType} from '../common/chrome-api-wrapper';
-import {ScraperCommand, ScraperMessage} from '../common/types/scraper';
+import { ChromeMessage, ChromeMessageType } from '../common/chrome-api-wrapper';
+import { ScraperCommand, ScraperMessage } from '../common/types/scraper';
async function handleScrapeCommand() {
const pageTitle = document.title;
diff --git a/src/popup/App.tsx b/src/popup/App.tsx
new file mode 100644
index 0000000..421012a
--- /dev/null
+++ b/src/popup/App.tsx
@@ -0,0 +1,22 @@
+import { lazy, Suspense } from 'react';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
+
+import AppShell from './AppShell/AppShell';
+import PopupHeader from './components/PopupHeader/PopupHeader';
+
+const HomePage = lazy(() => import('./features/HomePage/HomePage'));
+
+export default function App() {
+ return (
+
+
+ }>
+
+ } />
+ } />
+
+
+
+
+ );
+}
diff --git a/src/popup/AppShell/AppShell.tsx b/src/popup/AppShell/AppShell.tsx
index ef39e24..7b49288 100644
--- a/src/popup/AppShell/AppShell.tsx
+++ b/src/popup/AppShell/AppShell.tsx
@@ -1,9 +1,9 @@
import './AppShell.css';
-import {ReactElement, ReactNode, useEffect} from 'react';
-import {useNavigate} from 'react-router-dom';
+import { ReactElement, ReactNode, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
-export default function AppShell(props: {children?: ReactNode}): ReactElement {
+export default function AppShell(props: { children?: ReactNode }): ReactElement {
const navigate = useNavigate();
useEffect(() => {
diff --git a/src/popup/components/PopupContent/PopupContent.tsx b/src/popup/components/PopupContent/PopupContent.tsx
index ff76f9d..a1fbb07 100644
--- a/src/popup/components/PopupContent/PopupContent.tsx
+++ b/src/popup/components/PopupContent/PopupContent.tsx
@@ -1,9 +1,9 @@
import './PopupContent.css';
import Box from '@mui/material/Box';
-import {ReactElement, ReactNode} from 'react';
+import { ReactElement, ReactNode } from 'react';
-export default function PopupContent(props: {children?: ReactNode}): ReactElement {
+export default function PopupContent(props: { children?: ReactNode }): ReactElement {
return (
{props.children}
diff --git a/src/popup/components/PopupHeader/PopupHeader.tsx b/src/popup/components/PopupHeader/PopupHeader.tsx
index 4a48674..0bad5bd 100644
--- a/src/popup/components/PopupHeader/PopupHeader.tsx
+++ b/src/popup/components/PopupHeader/PopupHeader.tsx
@@ -1,12 +1,12 @@
import './PopupHeader.css';
import ExtensionRoundedIcon from '@mui/icons-material/ExtensionRounded';
-import {Toolbar} from '@mui/material';
-import {ReactElement, ReactNode} from 'react';
+import { Toolbar } from '@mui/material';
+import { ReactElement, ReactNode } from 'react';
-export default function PopupHeader(props: {children?: ReactNode}): ReactElement {
+export default function PopupHeader(props: { children?: ReactNode }): ReactElement {
return (
-
+
Chrome Extension React
{props.children}
diff --git a/src/popup/features/HomePage/HomePage.tsx b/src/popup/features/HomePage/HomePage.tsx
index 02eb3a9..aed390b 100644
--- a/src/popup/features/HomePage/HomePage.tsx
+++ b/src/popup/features/HomePage/HomePage.tsx
@@ -1,15 +1,15 @@
import './HomePage.css';
-import {Alert, Button, Snackbar, Stack} from '@mui/material';
+import { Alert, Button, Snackbar, Stack } from '@mui/material';
import Box from '@mui/material/Box';
-import {ReactElement, useEffect, useState} from 'react';
+import { ReactElement, useEffect, useState } from 'react';
import {
ChromeApiWrapper,
ChromeMessage,
ChromeMessageType
} from '../../../common/chrome-api-wrapper';
-import {ScraperCommand, ScraperMessage} from '../../../common/types/scraper';
+import { ScraperCommand, ScraperMessage } from '../../../common/types/scraper';
import PopupContent from '../../components/PopupContent/PopupContent';
import PopupHeader from '../../components/PopupHeader/PopupHeader';
@@ -31,7 +31,7 @@ export default function HomePage(): ReactElement {
const message: ChromeMessage = {
type: ChromeMessageType.SCRAPER_COMMAND,
- payload: {command: ScraperCommand.SCRAPE}
+ payload: { command: ScraperCommand.SCRAPE }
};
try {
@@ -55,7 +55,7 @@ export default function HomePage(): ReactElement {
return false;
}
- chrome.storage.session.set({[CACHE_KEY]: message.payload});
+ chrome.storage.session.set({ [CACHE_KEY]: message.payload });
setScrapedPageTitle(message.payload);
setDisableScrapeButton(false);
return false;
diff --git a/src/popup/styles/index.css b/src/popup/index.css
similarity index 100%
rename from src/popup/styles/index.css
rename to src/popup/index.css
diff --git a/index.html b/src/popup/index.html
similarity index 77%
rename from index.html
rename to src/popup/index.html
index 753ecb1..16a8b40 100644
--- a/index.html
+++ b/src/popup/index.html
@@ -9,10 +9,10 @@
Chrome Extension React
-
+
-
+