Si estás intentando desarrollar una extensión de Chrome en React, CRXJ es muy útil.
CRXJS proporciona una experiencia rápida de desarrollo de extensiones con vite.
Puedes escribir el nombre del archivo en manifest.json y cada actualización del archivo se refleja instantáneamente porque el directorio de compilación hace referencia directa al archivo que estás editando.
Tomemos un ejemplo del proceso de desarrollo real.
Dockerfile
├── docker-compose.yml
├── index.html
├── manifest.config.ts
├── manifest.json
├── options.html
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── assets
│ │ ├── favicon.svg
│ │ └── logo.svg
│ ├── background.ts
│ ├── components
│ │ └── Button.tsx
│ ├── content_scripts
│ │ └── content_script.tsx
│ ├── options.tsx
│ ├── popup.tsx
│ └── vite-env.d.ts
├── tsconfig.json
└── vite.config.ts
$ npm init vite@latest
Need to install the following packages:
create-vite@4.3.1
Ok to proceed? (y) y
✔ Project name: … vite-project
✔ Select a framework: › React
✔ Select a variant: › TypeScript
npm init vite@latest
npm i @crxjs/vite-plugin@beta -D
npm i vite-plugin-svgr
Cambiar vite-env.d.ts
/// <reference types="vite/client" />
/// <reference types="vite-plugin-svgr/client" />
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { crx, ManifestV3Export } from "@crxjs/vite-plugin";
import manifest from "./manifest.json";
import svgr from "vite-plugin-svgr";
export default defineConfig({
plugins: [
svgr(),
react(),
crx({ manifest: manifest as unknown as ManifestV3Export }),
],
});
Crear manifest.json en el directorio raíz
{
"name": "Extension App",
"description": "",
"version": "0.0.1",
"manifest_version": 3,
"action": {
"default_popup": "index.html",
"default_title": "Open Extension App"
}
}
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true,
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src", "vite.config.ts", "*.json"]
}
npm run dev
Abra Manage Extension la pagina en su navegador
Encienda Developer Mode el interruptor de la esquina superior derecha.
Haga clic en Load Unpacked boton en la esquina superior izquierda y seleccione el dist directorio raíz de su proyecto.
npm run build
FROM node:18.15.0-alpine3.16
WORKDIR /usr/src/app
COPY package*.json ./
RUN yarn install
COPY . .
version: '3'
services:
extension:
container_name: extension
hostname: extension
restart: always
tty: true
build:
context: .
dockerfile: Dockerfile
ports:
- 5173:5173
volumes:
- .:/usr/src/app
command: yarn dev --host
networks:
- default
platform: linux/amd64
networks:
default:
docker compose up -d --build
Eliminar App.tx y renombrar main.tx a popup.tx
Modificar popup.tsx
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
import logo from "./assets/logo.svg";
function Popup() {
const [count, setCount] = useState(0);
return (
<div className="App" style={{ height: 300, width: 300 }}>
<header className="App-header">
<img
src={chrome.runtime.getURL(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>
);
}
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<Popup />
</React.StrictMode>
);
Corregir src el atributo de la etiqueta de script en index.html
<script type="module" src="/src/popup.tsx"></script>
Añadir content_scripts seccion en manifest.json
{
"name": "Extension App",
"description": "",
"version": "0.0.1",
"manifest_version": 3,
"action": {
"default_popup": "index.html",
"default_title": "Open Extension App"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["src/content_scripts/content_script.tsx"]
}
]
}
Crear content_scripts directorio dentro de la carpeta src
Cree un componente content_script de muestra en src/content_scripts/content_script.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import Button from "../components/Button";
function ContentScript() {
return (
<div className="App">
<header className="App-header">
<h1>ContentScript</h1>
<Button>button</Button>
</header>
</div>
);
}
const index = document.createElement("div");
index.id = "content-script";
document.body.appendChild(index);
ReactDOM.createRoot(index).render(
<React.StrictMode>
<ContentScript />
</React.StrictMode>
);
Al mismo tiempo crear la carpeta component y dentro de ella un archivo Button.tsx con el siguiente contenido
import React from "react";
const Button = (props: any) => <button {...props} />;
export default Button;
Añade una definición de background a manifest.json
Tenga en cuenta que si desea utilizar ciertas funciones de la API de Chrome, es necesario agregar algunos permisos a permissions
{
"name": "Extension App",
"description": "",
"version": "0.0.1",
"manifest_version": 3,
"action": {
"default_popup": "index.html",
"default_title": "Open Extension App"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["src/content_scripts/content_script.tsx"]
}
],
"background": {
"service_worker": "src/background.ts",
"type": "module"
},
"permissions": [
"background",
"contextMenus",
"bookmarks",
"tabs",
"storage",
"history"
]
}
Crear el archivo background.ts en el directorio src
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
console.log(`Change URL: ${tab.url}`);
});
chrome.bookmarks.getRecent(10, (results) => {
console.log(`bookmarks:`, results);
});
console.log(`this is background service worker`);
export {};
Añadir la sección options_page a manifest.json
{
"name": "Extension App",
"description": "",
"version": "0.0.1",
"manifest_version": 3,
"action": {
"default_popup": "index.html",
"default_title": "Open Extension App"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["src/content_scripts/content_script.tsx"]
}
],
"background": {
"service_worker": "src/background.ts",
"type": "module"
},
"options_page": "options.html",
"permissions": [
"background",
"contextMenus",
"bookmarks",
"tabs",
"storage",
"history"
]
}
Crear options.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Extension App</title>
</head>
<body>
<script type="module" src="/src/options.tsx"></script>
</body>
</html>
Crear archivo options.tsx en el directorio src
import React from "react";
import ReactDOM from "react-dom/client";
import Button from "./components/Button";
function Options() {
console.log(`this is options page`);
return (
<div className="App">
<header className="App-header">
<h1>Title</h1>
<Button>button</Button>
</header>
</div>
);
}
const index = document.createElement("div");
index.id = "options";
document.body.appendChild(index);
ReactDOM.createRoot(index).render(
<React.StrictMode>
<Options />
</React.StrictMode>
);
CRXJS mejora la experiencia de desarrollo de extensiones con React.
Además, se puede configurar fácilmente con Docker.