mirror of
https://github.com/chenasraf/copy-tab-url.git
synced 2026-05-17 17:38:15 +00:00
feat: initial commit
This commit is contained in:
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vite-ssg-dist
|
||||
.vite-ssg-temp
|
||||
*.crx
|
||||
*.local
|
||||
*.log
|
||||
*.pem
|
||||
*.xpi
|
||||
*.zip
|
||||
dist
|
||||
dist-ssr
|
||||
extension/manifest.json
|
||||
node_modules
|
||||
src/auto-imports.d.ts
|
||||
src/components.d.ts
|
||||
.eslintcache
|
||||
7
.gitpod.Dockerfile
vendored
Normal file
7
.gitpod.Dockerfile
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
FROM gitpod/workspace-full-vnc
|
||||
|
||||
USER root
|
||||
|
||||
# Install dependencies
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y firefox
|
||||
23
.gitpod.yml
Normal file
23
.gitpod.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
|
||||
tasks:
|
||||
- init: pnpm install && pnpm run build
|
||||
name: dev
|
||||
command: |
|
||||
gp sync-done ready
|
||||
pnpm run dev
|
||||
- name: pnpm start:chromium
|
||||
command: |
|
||||
gp sync-await ready
|
||||
gp ports await 6080
|
||||
gp preview $(gp url 6080)
|
||||
sleep 5
|
||||
pnpm start:chromium
|
||||
openMode: split-right
|
||||
|
||||
ports:
|
||||
- port: 5900
|
||||
onOpen: ignore
|
||||
- port: 6080
|
||||
onOpen: ignore
|
||||
112
README.md
Normal file
112
README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# WebExtension Vite Starter (with React)
|
||||
|
||||
A [Vite](https://vitejs.dev/) powered WebExtension ([Chrome](https://developer.chrome.com/docs/extensions/reference/), [FireFox](https://addons.mozilla.org/en-US/developers/), etc.) starter template.
|
||||
|
||||
<p align="center">
|
||||
<sub>Popup</sub><br/>
|
||||
<img width="655" src="https://user-images.githubusercontent.com/11247099/126741643-813b3773-17ff-4281-9737-f319e00feddc.png"><br/>
|
||||
<sub>Options Page</sub><br/>
|
||||
<img width="655" src="https://user-images.githubusercontent.com/11247099/126741653-43125b62-6578-4452-83a7-bee19be2eaa2.png"><br/>
|
||||
<sub>Inject Vue App into the Content Script</sub><br/>
|
||||
<img src="https://user-images.githubusercontent.com/11247099/130695439-52418cf0-e186-4085-8e19-23fe808a274e.png">
|
||||
</p>
|
||||
|
||||
## Features
|
||||
|
||||
- ⚡️ **Instant HMR** - use **Vite** on dev (no more refresh!)
|
||||
- ⚛️ [React](https://reactjs.org/)
|
||||
- 🦾 [TypeScript](https://www.typescriptlang.org/) - type safe
|
||||
- 🖥 Content Script - Use React even in content script
|
||||
- 🌍 WebExtension - isomorphic extension for Chrome, Firefox, and others
|
||||
- 📃 Dynamic `manifest.json` with full type support
|
||||
|
||||
## Pre-packed
|
||||
|
||||
### WebExtension Libraries
|
||||
|
||||
- [`webextension-polyfill`](https://github.com/mozilla/webextension-polyfill) - WebExtension browser API Polyfill with types
|
||||
- [`webext-bridge`](https://github.com/antfu/webext-bridge) - effortlessly communication between contexts
|
||||
|
||||
### Vite Plugins
|
||||
|
||||
- [`unplugin-auto-import`](https://github.com/antfu/unplugin-auto-import) - Directly use `browser` without importing
|
||||
|
||||
### Coding Style
|
||||
|
||||
- [ESLint](https://eslint.org/)
|
||||
- [Prettier](https://prettier.io/)
|
||||
|
||||
### Dev tools
|
||||
|
||||
- [TypeScript](https://www.typescriptlang.org/)
|
||||
- [pnpm](https://pnpm.io/) - fast, disk space efficient package manager
|
||||
- [esno](https://github.com/antfu/esno) - TypeScript / ESNext node runtime powered by esbuild
|
||||
- [npm-run-all](https://github.com/mysticatea/npm-run-all) - Run multiple npm-scripts in parallel or sequential
|
||||
- [web-ext](https://github.com/mozilla/web-ext) - Streamlined experience for developing web extensions
|
||||
|
||||
## Use the Template
|
||||
|
||||
### GitHub Template
|
||||
|
||||
[Create a repo from this template on GitHub](https://github.com/YangJonghun/vite-react-webext/generate).
|
||||
|
||||
### Clone to local
|
||||
|
||||
If you prefer to do it manually with the cleaner git history
|
||||
|
||||
> If you don't have pnpm installed, run: npm install -g pnpm
|
||||
|
||||
```bash
|
||||
npx degit YangJonghun/vite-react-webext my-webext
|
||||
cd my-webext
|
||||
pnpm
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Folders
|
||||
|
||||
- `src` - main source.
|
||||
- `assets` - shareable public assets.
|
||||
- `background` - scripts for background.
|
||||
- `contentScripts` - scripts and components to be injected as `content_script`
|
||||
- `components` - auto-imported Vue components that are shared in popup and options page.
|
||||
- `styles` - styles shared in popup and options page
|
||||
- `manifest.ts` - manifest for the extension (v2).
|
||||
- `extension` - extension package root.
|
||||
- `assets` - static assets.
|
||||
- `dist` - built files, also serve stub entry for Vite on development.
|
||||
- `scripts` - development and bundling helper scripts.
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Then **load extension in browser with the `extension/` folder**.
|
||||
|
||||
For Firefox developers, you can run the following command instead:
|
||||
|
||||
```bash
|
||||
pnpm start:firefox
|
||||
```
|
||||
|
||||
`web-ext` auto reload the extension when `extension/` files changed.
|
||||
|
||||
> While Vite handles HMR automatically in the most of the case, [Extensions Reloader](https://chrome.google.com/webstore/detail/fimgfedafeadlieiabdeeaodndnlbhid) is still recommanded for cleaner hard reloading.
|
||||
|
||||
### Build
|
||||
|
||||
To build the extension, run
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
And then pack files under `extension`, you can upload `extension.crx` or `extension.xpi` to appropriate extension store.
|
||||
|
||||
## Credits
|
||||
|
||||
This template codes are based on
|
||||
[Anthony Fu](https://github.com/antfu)'s [vitesse-webext](https://github.com/antfu/vitesse-webext).
|
||||
BIN
extension/assets/icon-512.png
Normal file
BIN
extension/assets/icon-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
3
extension/assets/icon.svg
Normal file
3
extension/assets/icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M26.6667 1.66667H24V7H8V9.66667H5.33333V20.3333H8V23H10.6667V28.3333H21.3333V25.6667H26.6667V23H21.3333V20.3333H26.6667V17.6667H21.3333V15H10.6667V20.3333H8V9.66667H24V7H26.6667V1.66667ZM18.6667 25.6667H13.3333V17.6667H18.6667V25.6667Z" fill="#888888"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 366 B |
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
63
package.json
Normal file
63
package.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "vite-react-webext",
|
||||
"displayName": "Copy Tab URL",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"description": "Easy shortcuts for copying tab URL with or without title, with customizable format.",
|
||||
"packageManager": "pnpm@9.9.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run clear && cross-env NODE_ENV=development run-p 'dev:*'",
|
||||
"dev:prepare": "esno scripts/prepare.ts",
|
||||
"dev:web": "vite",
|
||||
"dev:js": "npm run build:js -- --mode development",
|
||||
"build": "cross-env NODE_ENV=production run-s clear build:web build:prepare build:js",
|
||||
"build:prepare": "esno scripts/prepare.ts",
|
||||
"build:web": "vite build",
|
||||
"build:js": "vite build --config vite.config.content.ts",
|
||||
"preview": "vite preview",
|
||||
"pack": "cross-env NODE_ENV=production run-p 'pack:*'",
|
||||
"pack:zip": "rimraf extension.zip && jszip-cli add extension/* -o ./extension.zip",
|
||||
"pack:crx": "crx pack extension -o ./extension.crx",
|
||||
"pack:xpi": "cross-env WEB_EXT_ARTIFACTS_DIR=./ web-ext build --source-dir ./extension --filename extension.xpi --overwrite-dest",
|
||||
"start:chromium": "web-ext run --source-dir ./extension --target=chromium",
|
||||
"start:firefox": "web-ext run --source-dir ./extension --target=firefox-desktop",
|
||||
"clear": "rimraf extension/dist extension/manifest.json 'extension.*'",
|
||||
"lint": "eslint --ignore-path .gitignore './**/*.{ts,tsx,js,jsx}'"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/node": "^22.7.4",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/webextension-polyfill": "^0.12.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.8.0",
|
||||
"@typescript-eslint/parser": "^8.8.0",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"chokidar": "^4.0.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"crx": "^5.0.1",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.37.1",
|
||||
"esno": "^4.8.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"kolorist": "^1.8.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
"rimraf": "^6.0.1",
|
||||
"typescript": "^5.6.2",
|
||||
"unplugin-auto-import": "^0.18.3",
|
||||
"vite": "^5.4.8",
|
||||
"web-ext": "^8.3.0",
|
||||
"webext-bridge": "^6.0.1",
|
||||
"webextension-polyfill": "^0.12.0"
|
||||
}
|
||||
}
|
||||
6612
pnpm-lock.yaml
generated
Normal file
6612
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
scripts/manifest.ts
Normal file
12
scripts/manifest.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { getManifest } from '../src/manifest'
|
||||
import { r, log } from './utils'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
export async function writeManifest() {
|
||||
await fs.writeJSON(r('extension/manifest.json'), await getManifest(), {
|
||||
spaces: 2,
|
||||
})
|
||||
log('PRE', 'write manifest.json')
|
||||
}
|
||||
|
||||
writeManifest()
|
||||
55
scripts/prepare.ts
Normal file
55
scripts/prepare.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
// generate stub index.html files for dev entry
|
||||
import { execSync } from 'child_process'
|
||||
import { r, port, isDev, log, fastRefresh, preambleCode } from './utils'
|
||||
import fs from 'fs-extra'
|
||||
import chokidar from 'chokidar'
|
||||
|
||||
/**
|
||||
* Stub index.html to use Vite in development
|
||||
*/
|
||||
export async function stubIndexHtml() {
|
||||
const views = ['options', 'popup', 'background']
|
||||
|
||||
const preambleCodeScriptTag = fastRefresh
|
||||
? `<script type="module">${preambleCode}</script>`
|
||||
: ''
|
||||
|
||||
for (const view of views) {
|
||||
await fs.ensureDir(r(`extension/dist/${view}`))
|
||||
let data = await fs.readFile(r(`src/${view}/index.html`), 'utf-8')
|
||||
data = data
|
||||
.replace(
|
||||
/<!-- react-hmr -->/g,
|
||||
`<base href="http://localhost:${port}" />
|
||||
<script type="module" src="/@vite/client"></script>
|
||||
${preambleCodeScriptTag}`,
|
||||
)
|
||||
.replace(
|
||||
/".\/main.((js|jsx|ts|tsx)?)"/g,
|
||||
(_, ext) => `"./${view}/main.${ext}"`,
|
||||
)
|
||||
.replace(
|
||||
'<div id="root"></div>',
|
||||
'<div id="root">Vite server did not start</div>',
|
||||
)
|
||||
|
||||
await fs.writeFile(r(`extension/dist/${view}/index.html`), data, 'utf-8')
|
||||
log('PRE', `stub ${view}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function writeManifest() {
|
||||
execSync('npx esno ./scripts/manifest.ts', { stdio: 'inherit' })
|
||||
}
|
||||
|
||||
writeManifest()
|
||||
|
||||
if (isDev) {
|
||||
stubIndexHtml()
|
||||
chokidar.watch(r('src/**/*.html')).on('change', () => {
|
||||
stubIndexHtml()
|
||||
})
|
||||
chokidar.watch([r('src/manifest.ts'), r('package.json')]).on('change', () => {
|
||||
writeManifest()
|
||||
})
|
||||
}
|
||||
16
scripts/utils.ts
Normal file
16
scripts/utils.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { resolve } from 'path'
|
||||
import { bgCyan, black } from 'kolorist'
|
||||
import react from '@vitejs/plugin-react'
|
||||
const __dirname = import.meta.dirname
|
||||
|
||||
export const fastRefresh = true
|
||||
|
||||
export const port = parseInt(process.env.PORT || '') || 3303
|
||||
export const r = (...args: string[]) => resolve(__dirname, '..', ...args)
|
||||
export const isDev = process.env.NODE_ENV !== 'production'
|
||||
export const preambleCode = react.preambleCode.replace('__BASE__', '/')
|
||||
|
||||
export function log(name: string, message: string) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(black(bgCyan(` ${name} `)), message)
|
||||
}
|
||||
10
shim.d.ts
vendored
Normal file
10
shim.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { ProtocolWithReturn } from 'webext-bridge'
|
||||
|
||||
declare module 'webext-bridge' {
|
||||
export interface ProtocolMap {
|
||||
// define message protocol types
|
||||
// see https://github.com/antfu/webext-bridge#type-safe-protocols
|
||||
'tab-prev': { title: string | undefined }
|
||||
'get-current-tab': ProtocolWithReturn<{ tabId: number }, { title?: string }>
|
||||
}
|
||||
}
|
||||
43
src/App.tsx
Normal file
43
src/App.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import logo from './logo.svg'
|
||||
import { useState } from 'react'
|
||||
import './App.css'
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>Hello Vite + React!</p>
|
||||
<p>
|
||||
<button type="button" onClick={() => setCount(count => count + 1)}>
|
||||
count is: {count} {''}
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
Edit <code>App.tsx</code> and save to test HMR updates.
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Learn React
|
||||
</a>
|
||||
{' | '}
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://vitejs.dev/guide/features.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Vite Docs
|
||||
</a>
|
||||
</p>
|
||||
</header>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
15
src/assets/favicon.svg
Normal file
15
src/assets/favicon.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#41D1FF"/>
|
||||
<stop offset="1" stop-color="#BD34FE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFEA83"/>
|
||||
<stop offset="0.0833333" stop-color="#FFDD35"/>
|
||||
<stop offset="1" stop-color="#FFA800"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
7
src/assets/logo.svg
Normal file
7
src/assets/logo.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
18
src/background/contentScriptHMR.ts
Normal file
18
src/background/contentScriptHMR.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { isFirefox, isForbiddenUrl } from '@/env'
|
||||
|
||||
// Firefox fetch files from cache instead of reloading changes from disk,
|
||||
// hmr will not work as Chromium based browser
|
||||
browser.webNavigation.onCommitted.addListener(({ tabId, frameId, url }) => {
|
||||
// Filter out non main window events.
|
||||
if (frameId !== 0) return
|
||||
|
||||
if (isForbiddenUrl(url)) return
|
||||
|
||||
// inject the latest scripts
|
||||
browser.tabs
|
||||
.executeScript(tabId, {
|
||||
file: `${isFirefox ? '' : '.'}/dist/contentScripts/index.global.js`,
|
||||
runAt: 'document_end',
|
||||
})
|
||||
.catch(error => console.error(error))
|
||||
})
|
||||
11
src/background/index.html
Normal file
11
src/background/index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- react-hmr -->
|
||||
<meta charset="UTF-8" />
|
||||
<title>Background</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="./main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
56
src/background/main.ts
Normal file
56
src/background/main.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { sendMessage, onMessage } from 'webext-bridge'
|
||||
import type { Tabs } from 'webextension-polyfill'
|
||||
|
||||
// only on dev mode
|
||||
if (import.meta.hot) {
|
||||
// @ts-expect-error for background HMR
|
||||
import('/@vite/client')
|
||||
// load latest content script
|
||||
import('./contentScriptHMR')
|
||||
}
|
||||
|
||||
browser.runtime.onInstalled.addListener((): void => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Extension installed')
|
||||
})
|
||||
|
||||
let previousTabId = 0
|
||||
|
||||
// communication example: send previous tab title from background page
|
||||
// see shim.d.ts for type declaration
|
||||
browser.tabs.onActivated.addListener(async ({ tabId }) => {
|
||||
if (!previousTabId) {
|
||||
previousTabId = tabId
|
||||
return
|
||||
}
|
||||
|
||||
let tab: Tabs.Tab
|
||||
|
||||
try {
|
||||
tab = await browser.tabs.get(previousTabId)
|
||||
previousTabId = tabId
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('previous tab', tab)
|
||||
sendMessage(
|
||||
'tab-prev',
|
||||
{ title: tab.title },
|
||||
{ context: 'content-script', tabId },
|
||||
)
|
||||
})
|
||||
|
||||
onMessage('get-current-tab', async () => {
|
||||
try {
|
||||
const tab = await browser.tabs.get(previousTabId)
|
||||
return {
|
||||
title: tab?.title,
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
title: undefined,
|
||||
}
|
||||
}
|
||||
})
|
||||
10
src/contentScripts/App.tsx
Normal file
10
src/contentScripts/App.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
const App = () => {
|
||||
return (
|
||||
<div>
|
||||
<div>Vitesse WebExt</div>
|
||||
<div></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
30
src/contentScripts/main.tsx
Normal file
30
src/contentScripts/main.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import App from './App'
|
||||
import * as ReactDOM from 'react-dom/client'
|
||||
import { onMessage } from 'webext-bridge/content-script'
|
||||
|
||||
// Firefox `browser.tabs.executeScript()` requires scripts return a primitive value
|
||||
;(() => {
|
||||
console.info('[vite-react-webext] Hello world from content script')
|
||||
|
||||
// communication example: send previous tab title from background page
|
||||
onMessage('tab-prev', ({ data }) => {
|
||||
console.log(`[vite-react-webext] Navigate from page "${data.title}"`)
|
||||
})
|
||||
|
||||
// mount component to context window
|
||||
const container = document.createElement('div')
|
||||
const root = document.createElement('div')
|
||||
const styleEl = document.createElement('link')
|
||||
const shadowDOM =
|
||||
container.attachShadow?.({ mode: __DEV__ ? 'open' : 'closed' }) || container
|
||||
styleEl.setAttribute('rel', 'stylesheet')
|
||||
styleEl.setAttribute(
|
||||
'href',
|
||||
browser.runtime.getURL('dist/contentScripts/style.css'),
|
||||
)
|
||||
shadowDOM.appendChild(styleEl)
|
||||
shadowDOM.appendChild(root)
|
||||
document.body.appendChild(container)
|
||||
const reactRoot = ReactDOM.createRoot(root)
|
||||
reactRoot.render(<App />)
|
||||
})()
|
||||
14
src/env.ts
Normal file
14
src/env.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
const forbiddenProtocols = [
|
||||
'chrome-extension://',
|
||||
'chrome-search://',
|
||||
'chrome://',
|
||||
'devtools://',
|
||||
'edge://',
|
||||
'https://chrome.google.com/webstore',
|
||||
]
|
||||
|
||||
export function isForbiddenUrl(url: string): boolean {
|
||||
return forbiddenProtocols.some(protocol => url.startsWith(protocol))
|
||||
}
|
||||
|
||||
export const isFirefox = navigator.userAgent.includes('Firefox')
|
||||
1
src/global.d.ts
vendored
Normal file
1
src/global.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare const __DEV__: boolean
|
||||
30
src/hooks/useStorageLocal.ts
Normal file
30
src/hooks/useStorageLocal.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { Storage } from 'webextension-polyfill'
|
||||
import { storage } from 'webextension-polyfill'
|
||||
|
||||
interface Params<T> {
|
||||
key: string
|
||||
initialValue?: T | null
|
||||
}
|
||||
|
||||
export function useStorageLocal<T>({ key, initialValue = null }: Params<T>) {
|
||||
const [value, _setValue] = useState<T | null>(initialValue)
|
||||
|
||||
useEffect(() => {
|
||||
storage.local.get(key).then(val => _setValue(val[key] ?? initialValue))
|
||||
const callback = (changes: Record<string, Storage.StorageChange>) => {
|
||||
if (changes[key]) _setValue(changes[key].newValue)
|
||||
}
|
||||
storage.onChanged.addListener(callback)
|
||||
return () => {
|
||||
storage.onChanged.removeListener(callback)
|
||||
}
|
||||
}, [key, initialValue])
|
||||
|
||||
const setValue = async (val: T | null) => {
|
||||
_setValue(val)
|
||||
await storage.local.set({ [key]: val })
|
||||
}
|
||||
|
||||
return [value, setValue] as [T | null, (val: T) => void]
|
||||
}
|
||||
62
src/manifest.ts
Normal file
62
src/manifest.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import crypto from 'crypto'
|
||||
import type PkgType from '../package.json'
|
||||
import { isDev, port, r, preambleCode } from '../scripts/utils'
|
||||
import fs from 'fs-extra'
|
||||
import type { Manifest } from 'webextension-polyfill'
|
||||
|
||||
export async function getManifest() {
|
||||
const pkg = (await fs.readJSON(r('package.json'))) as typeof PkgType
|
||||
|
||||
// update this file to update this manifest.json
|
||||
// can also be conditional based on your need
|
||||
const manifest: Manifest.WebExtensionManifest = {
|
||||
manifest_version: 2,
|
||||
name: pkg.displayName || pkg.name,
|
||||
version: pkg.version,
|
||||
description: pkg.description,
|
||||
browser_action: {
|
||||
default_icon: './assets/icon-512.png',
|
||||
default_popup: './dist/popup/index.html',
|
||||
},
|
||||
options_ui: {
|
||||
page: './dist/options/index.html',
|
||||
open_in_tab: true,
|
||||
chrome_style: false,
|
||||
},
|
||||
background: {
|
||||
page: './dist/background/index.html',
|
||||
persistent: false,
|
||||
},
|
||||
icons: {
|
||||
16: './assets/icon-512.png',
|
||||
48: './assets/icon-512.png',
|
||||
128: './assets/icon-512.png',
|
||||
},
|
||||
permissions: ['tabs', 'storage', 'activeTab', 'http://*/', 'https://*/'],
|
||||
content_scripts: [
|
||||
{
|
||||
matches: ['http://*/*', 'https://*/*'],
|
||||
js: ['./dist/contentScripts/index.global.js'],
|
||||
},
|
||||
],
|
||||
web_accessible_resources: ['dist/contentScripts/style.css'],
|
||||
}
|
||||
|
||||
if (isDev) {
|
||||
// for content script, as browsers will cache them for each reload,
|
||||
// we use a background script to always inject the latest version
|
||||
// see src/background/contentScriptHMR.ts
|
||||
delete manifest.content_scripts
|
||||
manifest.permissions?.push('webNavigation')
|
||||
|
||||
const preambleCodeHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(preambleCode)
|
||||
.digest('base64')
|
||||
|
||||
// this is required on dev for Vite script to load
|
||||
manifest.content_security_policy = `script-src \'self\' 'sha256-${preambleCodeHash}' http://localhost:${port}; object-src \'self\'`
|
||||
}
|
||||
|
||||
return manifest
|
||||
}
|
||||
47
src/options/Options.css
Normal file
47
src/options/Options.css
Normal file
@@ -0,0 +1,47 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
transition: border-color calc(var(--transition, 0.2) * 1s) ease;
|
||||
outline: transparent;
|
||||
text-align: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
}
|
||||
32
src/options/Options.tsx
Normal file
32
src/options/Options.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import logo from '@/assets/logo.svg'
|
||||
import { useStorageLocal } from '@/hooks/useStorageLocal'
|
||||
import './Options.css'
|
||||
|
||||
const Options = () => {
|
||||
const [value, setValue] = useStorageLocal<string>({ key: 'webext-demo' })
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>This is the Options page</p>
|
||||
<p>
|
||||
<b>Change Storage Value and Check Popup</b>
|
||||
</p>
|
||||
<input value={value ?? ''} onChange={e => setValue(e.target.value)} />
|
||||
<p>
|
||||
Powered by{' '}
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://vitejs.dev/guide/features.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Vite
|
||||
</a>
|
||||
</p>
|
||||
</header>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Options
|
||||
13
src/options/index.html
Normal file
13
src/options/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- react-hmr -->
|
||||
<meta charset="UTF-8" />
|
||||
<base target="_blank" />
|
||||
<title>Options</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
9
src/options/main.tsx
Normal file
9
src/options/main.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import Options from './Options'
|
||||
import * as ReactDOM from 'react-dom/client'
|
||||
import '../styles'
|
||||
|
||||
const container = document.getElementById('root')
|
||||
if (container) {
|
||||
const root = ReactDOM.createRoot(container)
|
||||
root.render(<Options />)
|
||||
}
|
||||
43
src/popup/Popup.css
Normal file
43
src/popup/Popup.css
Normal file
@@ -0,0 +1,43 @@
|
||||
.App {
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: calc(10px + 2vmin);
|
||||
}
|
||||
62
src/popup/Popup.tsx
Normal file
62
src/popup/Popup.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import logo from '@/assets/logo.svg'
|
||||
import { useStorageLocal } from '@/hooks/useStorageLocal'
|
||||
import { useState } from 'react'
|
||||
import './Popup.css'
|
||||
|
||||
const Popup = () => {
|
||||
const [count, setCount] = useState(0)
|
||||
const [val] = useStorageLocal<string>({ key: 'webext-demo' })
|
||||
|
||||
const openOptionsPage = () => {
|
||||
browser.runtime.openOptionsPage()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img
|
||||
src={logo}
|
||||
className="App-logo"
|
||||
alt="logo"
|
||||
onClick={openOptionsPage}
|
||||
/>
|
||||
<p>Hello Vite + React!</p>
|
||||
<p>
|
||||
<button type="button" onClick={() => setCount(count => count + 1)}>
|
||||
count is: {count}
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
Edit <code>App.tsx</code> and save to test HMR updates.
|
||||
</p>
|
||||
<p>
|
||||
Storage: <b>{val ?? 'not exist'}</b>
|
||||
</p>
|
||||
<p>
|
||||
<button type="button" onClick={openOptionsPage}>
|
||||
open option!
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Learn React
|
||||
</a>
|
||||
{' | '}
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://vitejs.dev/guide/features.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Vite Docs
|
||||
</a>
|
||||
</p>
|
||||
</header>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Popup
|
||||
13
src/popup/index.html
Normal file
13
src/popup/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- react-hmr -->
|
||||
<meta charset="UTF-8" />
|
||||
<base target="_blank" />
|
||||
<title>Popup</title>
|
||||
</head>
|
||||
<body style="min-width: 100px">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
9
src/popup/main.tsx
Normal file
9
src/popup/main.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import Popup from './Popup'
|
||||
import * as ReactDOM from 'react-dom/client'
|
||||
import '../styles'
|
||||
|
||||
const container = document.getElementById('root')
|
||||
if (container) {
|
||||
const root = ReactDOM.createRoot(container)
|
||||
root.render(<Popup />)
|
||||
}
|
||||
1
src/styles/index.ts
Normal file
1
src/styles/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './main.css'
|
||||
13
src/styles/main.css
Normal file
13
src/styles/main.css
Normal file
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
36
tsconfig.json
Normal file
36
tsconfig.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"target": "es2016",
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ESNext"
|
||||
],
|
||||
"strict": true,
|
||||
"esModuleInterop": false,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "Node",
|
||||
"useDefineForClassFields": true,
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"types": [
|
||||
"vite/client"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
27
vite.config.content.ts
Normal file
27
vite.config.content.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { sharedConfig } from './vite.config'
|
||||
import { r, isDev } from './scripts/utils'
|
||||
import packageJson from './package.json'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
// bundling the content script using Vite
|
||||
export default defineConfig({
|
||||
...sharedConfig,
|
||||
build: {
|
||||
watch: isDev ? {} : undefined,
|
||||
outDir: r('extension/dist/contentScripts'),
|
||||
cssCodeSplit: false,
|
||||
emptyOutDir: false,
|
||||
sourcemap: isDev ? 'inline' : false,
|
||||
lib: {
|
||||
entry: r('src/contentScripts/main.tsx'),
|
||||
name: packageJson.name,
|
||||
formats: ['iife'],
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: 'index.global.js',
|
||||
extend: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
93
vite.config.ts
Normal file
93
vite.config.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { dirname, relative } from 'path'
|
||||
import { readFile } from 'fs'
|
||||
import autoImport from 'unplugin-auto-import/vite'
|
||||
import { r, port, isDev, fastRefresh } from './scripts/utils'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite'
|
||||
import type { UserConfig } from 'vite'
|
||||
|
||||
export const sharedConfig: UserConfig = {
|
||||
root: r('src'),
|
||||
resolve: {
|
||||
alias: {
|
||||
'@/': `${r('src')}/`,
|
||||
},
|
||||
},
|
||||
define: {
|
||||
__DEV__: isDev,
|
||||
},
|
||||
plugins: [
|
||||
react({ fastRefresh }),
|
||||
autoImport({
|
||||
include: [/\.[tj]sx?$/],
|
||||
imports: [
|
||||
{
|
||||
'webextension-polyfill': [['*', 'browser']],
|
||||
},
|
||||
],
|
||||
// Filepath to generate corresponding .d.ts file.
|
||||
// Defaults to './src/auto-imports.d.ts' when `typescript` is installed locally.
|
||||
// Set `false` to disable.
|
||||
dts: r('src/auto-imports.d.ts'),
|
||||
}),
|
||||
// rewrite assets to use relative path
|
||||
{
|
||||
name: 'assets-rewrite',
|
||||
enforce: 'post',
|
||||
apply: 'build',
|
||||
transformIndexHtml(html, { path }) {
|
||||
return html.replace(
|
||||
/"\/assets\//g,
|
||||
`"${relative(dirname(path), '/assets')}/`,
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
plugins: [
|
||||
{
|
||||
name: 'load-js-files-as-jsx',
|
||||
setup(build) {
|
||||
build.onLoad({ filter: /src\\\.*\.js/ }, async args => ({
|
||||
loader: 'jsx',
|
||||
contents: void (await readFile(
|
||||
args.path,
|
||||
{ encoding: 'utf8' },
|
||||
() => {},
|
||||
)),
|
||||
}))
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
include: ['react', 'react-dom', 'webextension-polyfill'],
|
||||
},
|
||||
}
|
||||
|
||||
export default defineConfig(({ command }) => ({
|
||||
...sharedConfig,
|
||||
base: command === 'serve' ? `http://localhost:${port}/` : '/dist/',
|
||||
server: {
|
||||
port,
|
||||
hmr: {
|
||||
host: 'localhost',
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: r('extension/dist'),
|
||||
emptyOutDir: false,
|
||||
sourcemap: isDev ? 'inline' : false,
|
||||
// https://developer.chrome.com/docs/webstore/program_policies/#:~:text=Code%20Readability%20Requirements
|
||||
terserOptions: {
|
||||
mangle: false,
|
||||
},
|
||||
rollupOptions: {
|
||||
input: {
|
||||
background: r('src/background/index.html'),
|
||||
options: r('src/options/index.html'),
|
||||
popup: r('src/popup/index.html'),
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
Reference in New Issue
Block a user