Saltar al contenido

Crea tu primera extensión de Chrome con TypeScript desde cero

Vite, CRXJS y TypeScript permiten pasar de una carpeta vacía a un popup MV3 con tipos y recarga rápida.

Extensiones de Chrome3 min de lectura

Ingeniero de software. Desarrollo extensiones de Chrome, aplicaciones web y herramientas de IA aplicada.

Contenido

Muchos tutoriales de extensiones siguen usando JavaScript sin herramientas de compilación. Sirve para una prueba rápida, pero cuando el proyecto crece querrás tipos, importaciones y recarga durante el desarrollo.

Esta guía te lleva de una carpeta vacía a una extensión funcional con TypeScript, Vite y CRXJS. La idea es que puedas completar una primera versión en una sesión corta.

Si todavía no conoces su estructura, empieza por cómo funcionan las extensiones con Manifest V3, donde explico popups, scripts de contenido y service workers.

Qué vas a construir

Un popup que muestra el título y la URL de la pestaña actual. Es sencillo, pero conecta el manifiesto, la interfaz y la API chrome.tabs.

Qué necesitas

  • Node.js compatible con la versión de Vite que instales; usa una versión LTS vigente.
  • Un navegador Chromium, como Chrome, Edge o Brave.
  • Conocimientos básicos de TypeScript.

1. Crea la estructura inicial

npm create vite@latest my-first-extension -- --template vanilla-ts
cd my-first-extension
npm install
npm install -D @crxjs/vite-plugin@latest chrome-types

@crxjs/vite-plugin gestiona la compilación de Manifest V3, vigila archivos y facilita la recarga durante el desarrollo. chrome-types aporta definiciones de tipos para las APIs chrome.*.

2. Crea el manifiesto

Vacía src/ y crea manifest.json en la raíz:

{
  "manifest_version": 3,
  "name": "Tab Info",
  "version": "1.0.0",
  "description": "Shows the current tab's title and URL",
  "action": {
    "default_popup": "src/popup.html"
  },
  "permissions": ["activeTab"]
}

Las decisiones principales:

  • manifest_version: 3: usa el formato vigente para nuevas extensiones de la Chrome Web Store.
  • action.default_popup: indica qué interfaz abrir al pulsar el icono.
  • permissions: ["activeTab"]: permite acceder a la pestaña tras una acción del usuario sin pedir acceso permanente a todas las webs. Lo explico en la guía de permisos.

3. Configura Vite

Sustituye vite.config.ts:

import { defineConfig } from "vite";
import { crx } from "@crxjs/vite-plugin";
import manifest from "./manifest.json";

export default defineConfig({
  plugins: [crx({ manifest })],
});

CRXJS lee el manifiesto y conecta los puntos de entrada, la estructura de salida y la recarga en desarrollo.

4. Construye el popup

Crea src/popup.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <style>
      body {
        width: 320px;
        padding: 16px;
        font-family: system-ui, sans-serif;
      }
      h1 {
        font-size: 16px;
        margin: 0 0 12px;
      }
      p {
        font-size: 13px;
        color: #555;
        word-break: break-all;
      }
    </style>
  </head>
  <body>
    <h1 id="title">Loading...</h1>
    <p id="url"></p>
    <script type="module" src="./popup.ts"></script>
  </body>
</html>

Después, src/popup.ts:

async function showTabInfo(): Promise<void> {
  const titleEl = document.getElementById("title") as HTMLHeadingElement;
  const urlEl = document.getElementById("url") as HTMLParagraphElement;

  try {
    const [tab] = await chrome.tabs.query({
      active: true,
      currentWindow: true,
    });

    titleEl.textContent = tab.title ?? "No title";
    urlEl.textContent = tab.url ?? "No URL";
  } catch (err) {
    titleEl.textContent = "Error loading tab info";
    urlEl.textContent = err instanceof Error ? err.message : "Unknown error";
  }
}

showTabInfo();

chrome.tabs.query devuelve un arreglo. Con la consulta de este ejemplo tomamos la primera pestaña activa de la ventana actual. El bloque try-catch evita dejar el mensaje «Loading...» sin explicación si falla la llamada.

5. Añade los tipos de las APIs de Chrome

Actualiza tsconfig.json para que TypeScript cargue chrome-types:

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "strict": true,
    "skipLibCheck": true,
    "types": ["chrome-types"]
  },
  "include": ["src"]
}

La entrada importante es "types": ["chrome-types"] dentro de "compilerOptions". El editor podrá completar los métodos y señalar usos incorrectos antes de cargar la extensión.

6. Comprueba la compilación

Antes de abrir Chrome, compila:

npm run build

La carpeta dist/ contendrá la extensión compilada. Si falla, corrige el error aquí antes de intentar cargarla en el navegador.

7. Carga y prueba la extensión

npm run dev

En Chrome:

  1. Abre chrome://extensions.
  2. Activa «Modo de desarrollador».
  3. Pulsa «Cargar descomprimida».
  4. Selecciona dist/, dentro del proyecto.

Al pulsar el icono verás el título de la pestaña y su URL.

Durante el desarrollo, CRXJS permite editar popup.ts y actualizar la interfaz sin repetir manualmente toda la carga.

Si el popup no aparece:

  • Confirma que existe dist/; si falta, ejecuta npm run build.
  • Inspecciona el popup con el menú contextual y revisa la consola.
  • Comprueba el modo de desarrollador en chrome://extensions.
  • Busca avisos de error en la ficha de la extensión.

Estructura del proyecto

Después de estos pasos tendrás:

my-first-extension/
├── manifest.json
├── vite.config.ts
├── tsconfig.json
├── package.json
└── src/
    ├── popup.html
    └── popup.ts

Cuando crezca, puedes añadir:

  • src/background.ts para gestionar eventos en segundo plano.
  • src/content.ts para interactuar con el DOM de las páginas.
  • src/options.html para los ajustes.

Entonces necesitarás mensajes y almacenamiento, que cubro en las siguientes notas.

Problemas frecuentes

Versiones incompatibles de CRXJS y Vite. Comprueba que las versiones instaladas sean compatibles. El comando con @latest puede cambiar con el tiempo; sigue los requisitos que indique tu instalación.

Tamaño del popup. Chrome limita su tamaño a 800 píxeles de ancho y 600 de alto. Define un ancho en <body> para evitar que dependa solo del contenido.

activeTab requiere una acción del usuario. En este ejemplo la activa pulsar el icono. También existen atajos y acciones de menú compatibles. Para acceso continuo necesitarías permisos de host y una justificación para solicitarlos.

Los siguientes pasos

Ya tienes TypeScript y una extensión funcional:

  1. Revisa la arquitectura de Manifest V3.
  2. Conecta popup, worker y scripts con mensajes tipados.
  3. Conserva los datos con almacenamiento persistente, en lugar de depender de variables globales.

Esta nota forma parte de la serie Extensiones de Chrome: de cero a producto, donde explico lo que aprendí construyendo ResistGate y Amethyst, desde el primer popup hasta la publicación.

Un pequeño voto de confianza

¿Valió la pena leer esta nota?