agentsclimarketplace

Javascript

Skill 14BryanEspinoza/agent-stack/skills/javascript

Reglas de JavaScript vanilla ES6+ - ES2025, programación defensiva, módulos, DOM, fetch, APIs modernas, estructuras de datos, patrones avanzadosFrom its SKILL.md

Install
npx -y skills add 14BryanEspinoza/agent-stack --skill javascript

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

26.3 KB, ~6.9k tokens by cl100k_base, as published. Nobody here has run it

JavaScript Vanilla ES6+ - Reglas

JavaScript vanilla ES6+ es el enfoque preferido.

1. Instalación y Versión

Versión mínima

TecnologíaVersión Mínima
JavaScriptES2025+ (moderno)
Node.js22+

2. Cuándo USAR JavaScript

CasoJustificación
Interactividad dinámicaModales, acordeones, tabs
Manipulación del DOMCrear/actualizar contenido dinámicamente
Fetch/AJAXObtener/enviar datos sin recargar
Validación clienteValidación en tiempo real
APIs del navegadorLocalStorage, Geolocation, etc.

3. Cuándo NO USAR JavaScript

En lugar de...Usar...
Animaciones simplesCSS Transitions/Animations
Tooltips simplesCSS-only o title attribute
Modales simplesHTML <dialog>
Acordeones simplesHTML <details>/<summary>
Carousels simplesCSS scroll snap
Dropdowns simplesHTML <select>

4. APIs Preferidas

En lugar de...Usar...
varconst (por defecto), let (si reasigna)
functionArrow functions cuando corresponda
Callbacksasync/await + Promise
$.ajaxfetch
for loopsforEach, map, filter, reduce
Strings concatTemplate literals `hello ${name}`
setTimeout para animacionesCSS transitions
Animaciones con JSWeb Animations API
Detectar elementos en vistaIntersectionObserver

5. Estructuras de Datos Modernas

Map (claves de cualquier tipo)

const users = new Map();

users.set(42, { name: "Ana" });
users.set("admin", { name: "Admin" });
users.get(42); // { name: "Ana" }
users.has("admin"); // true
users.size; // 2
users.delete(42);
users.clear();

// Iteración
for (const [key, value] of users) {
  console.log(key, value);
}

WeakMap (claves objeto, sin prevenir GC)

let element = document.querySelector(".card");
const metadata = new WeakMap();

metadata.set(element, { timestamp: Date.now() });
// Cuando element se elimina del DOM y se recolecta,
// la entrada en WeakMap también se limpia automáticamente

metadata.get(element);

Set (valores únicos)

const tags = new Set(["js", "css", "html", "js"]);
// Set(3) { "js", "css", "html" }

tags.add("react");
tags.has("css"); // true
tags.size; // 4
tags.delete("css");

// Convertir array a valores únicos
const unique = [...new Set([1, 2, 2, 3, 3, 3])];
// [1, 2, 3]

Set Operations (ES2025)

const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);

setA.union(setB);
// Set(6) { 1, 2, 3, 4, 5, 6 }

setA.intersection(setB);
// Set(2) { 3, 4 }

setA.difference(setB);
// Set(2) { 1, 2 }

setA.symmetricDifference(setB);
// Set(4) { 1, 2, 5, 6 }

setA.isSubsetOf(setB); // false
setA.isSupersetOf(setB); // false
setA.isDisjointFrom(setB); // false

WeakRef + FinalizationRegistry

// WeakRef: referencia débil que no impide GC
const ref = new WeakRef({ data: "pesado" });
const obj = ref.deref(); // undefined si ya fue recolectado

// FinalizationRegistry: callback cuando un objeto es recolectado
const registry = new FinalizationRegistry((heldValue) => {
  console.log("Recolectado:", heldValue);
});

let tracked = { id: 1 };
registry.register(tracked, "cache-item-1", tracked);
registry.unregister(tracked); // Cancelar registro

6. Nuevas APIs del Lenguaje (ES2024+)

Promise.withResolvers()

Reemplaza el patrón verbose de promesas externas:

// Antes
const promise = new Promise((resolve, reject) => {
  externalCallback((err, data) => {
    if (err) reject(err);
    else resolve(data);
  });
});

// Ahora
const { promise, resolve, reject } = Promise.withResolvers();
externalCallback((err, data) => {
  if (err) reject(err);
  else resolve(data);
});

Object.groupBy() / Map.groupBy()

const products = [
  { name: "Laptop", category: "tech" },
  { name: "Shirt", category: "clothing" },
  { name: "Phone", category: "tech" },
];

// Object.groupBy (cuando las claves son strings)
const byCategory = Object.groupBy(products, (p) => p.category);
// { tech: [Laptop, Phone], clothing: [Shirt] }

// Map.groupBy (cuando las claves son objetos)
const byPriceRange = Map.groupBy(products, (p) => {
  if (p.price < 50) return "budget";
  if (p.price < 500) return "mid";
  return "premium";
});

structuredClone() — Deep cloning nativo

const original = { user: { name: "Ana", tags: ["js"] } };
const cloned = structuredClone(original);

cloned.user.name = "Luis"; // No afecta al original
cloned.user.tags.push("ts"); // No afecta al original

// ✅ Seguro con fechas, Map, Set, RegExp, ArrayBuffer
const complex = {
  date: new Date(),
  map: new Map([["key", "value"]]),
  set: new Set([1, 2, 3]),
};
structuredClone(complex); // ✅ Funciona

// ❌ No clona funciones, Symbol, WeakMap, DOM nodes

Array.fromAsync()

async function* asyncGenerator() {
  yield await Promise.resolve(1);
  yield await Promise.resolve(2);
  yield await Promise.resolve(3);
}

const arr = await Array.fromAsync(asyncGenerator());
// [1, 2, 3] — secuencial, respeta orden

// Equivalente a:
const results = [];
for await (const item of asyncGenerator()) {
  results.push(item);
}

Error.cause

try {
  await fetch("/api/data");
} catch (error) {
  // Preservar el error original con contexto adicional
  throw new Error("Failed to load data", { cause: error });
}

// Al capturar:
// error.message      → "Failed to load data"
// error.cause.message → error original de fetch

JSON.parse con context.source

const json = '{"count": 42, "name": "test"}';

JSON.parse(json, (key, value, context) => {
  console.log(`Key: ${key}, Source: ${context.source}`);
  // "count", "42"
  // "name", "\"test\""
  return value;
});

7. Patrones Avanzados

Proxy + Reflect (metaprogramación)

// Validación de propiedades con Proxy
const validator = {
  set(target, key, value) {
    if (key === "age" && (typeof value !== "number" || value < 0)) {
      throw new Error("Age must be a positive number");
    }
    return Reflect.set(target, key, value);
  },
};

const user = new Proxy({}, validator);
user.age = 25; // ✅
user.age = -5; // ❌ Error

// Logging automático con Proxy
const logger = {
  get(target, property, receiver) {
    console.log(`GET ${property}`);
    return Reflect.get(target, property, receiver);
  },
};

const tracked = new Proxy({ name: "Ana" }, logger);
tracked.name; // console: "GET name"

Intl.Segmenter (segmentación de texto)

// Segmentar por palabras
const wordSegmenter = new Intl.Segmenter("es", { granularity: "word" });
const words = [...wordSegmenter.segment("Hola mundo, ¿cómo estás?")];
// [{ segment: "Hola" }, { segment: " " }, { segment: "mundo" }, ...]

// Segmentar por oraciones
const sentenceSegmenter = new Intl.Segmenter("es", { granularity: "sentence" });
const text = "Primera oración. Segunda oración. ¿Tercera?";
const sentences = [...sentenceSegmenter.segment(text)];
// [{ segment: "Primera oración. " }, { segment: "Segunda oración. " }, ...]

// Segmentar por grafemas (caracteres visibles)
const graphemeSegmenter = new Intl.Segmenter("es", { granularity: "grapheme" });
const chars = [...graphemeSegmenter.segment("café")].map((s) => s.segment);
// ["c", "a", "f", "é"]

Iterator Helpers (ES2025)

function* naturals() {
  let i = 1;
  while (true) yield i++;
}

// take() — tomar N elementos
const first5 = naturals().take(5).toArray();
// [1, 2, 3, 4, 5]

// map() / filter() sobre iteradores (sin crear arrays intermedios)
const evens = naturals()
  .filter((n) => n % 2 === 0)
  .take(5)
  .toArray();
// [2, 4, 6, 8, 10]

// reduce()
const sum = naturals()
  .take(100)
  .reduce((a, b) => a + b, 0);
// 5050

// zip() — combinar iteradores
const names = ["Ana", "Luis", "Carlos"];
const ages = [25, 30, 28];
const paired = Iterator.zip(names, ages).toArray();
// [["Ana", 25], ["Luis", 30], ["Carlos", 28]]

// flatMap()
const words = ["hello", "world"];
const letters = words
  .values()
  .flatMap((w) => w.split(""))
  .toArray();
// ["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"]

// drop()
const withoutFirst = naturals().drop(10).take(3).toArray();
// [11, 12, 13]

8. Selección DOM

// Selección simple
const element = document.querySelector(".class");
const elements = document.querySelectorAll(".class");

// Por ID (más rápido)
const el = document.getElementById("my-id");

// Con dataset
element.dataset.property = "value";
const value = element.dataset.property;

9. Manipulación del DOM

Crear elementos

// Crear elemento
const div = document.createElement("div");
div.className = "card";
div.textContent = "Contenido";

// Crear con innerHTML (solo si no hay datos de usuario)
container.innerHTML = '<div class="card">Contenido seguro</div>';

// Evitar XSS con textContent
const userInput = '<script>alert("xss")</script>';
element.textContent = userInput; // Seguro
element.innerHTML = userInput; // Peligroso

// insertAdjacentHTML (más eficiente que innerHTML)
container.insertAdjacentHTML("beforeend", '<div class="card">Nuevo</div>');
// Posiciones: "beforebegin", "afterbegin", "beforeend", "afterend"

Agregar elementos

// Append (al final)
parent.appendChild(element);
parent.append("Texto", element);

// Prepend (al inicio)
parent.prepend(element);

// Before/After
sibling.before(element);
sibling.after(element);

// Reemplazar
oldElement.replaceWith(newElement);

// Reemplazar todo el contenido
parent.replaceChildren(element1, element2);

// Remover
element.remove();

Templates

// Usar template para estructuras complejas
const template = document.querySelector("#card-template");
const card = template.content.cloneNode(true);
card.querySelector(".title").textContent = "Nuevo título";
document.querySelector(".container").appendChild(card);

ARIA properties modernas

// En lugar de setAttribute, usar propiedades aria directas
button.ariaLabel = "Cerrar";
button.ariaExpanded = "false";
button.ariaPressed = "true";
button.role = "tab";
button.tabIndex = 0;

// Popover API
element.showPopover();
element.hidePopover();
element.togglePopover();

10. Eventos

Event listener

// Basic
element.addEventListener("click", (event) => {
  console.log(event.target);
});

// Con opciones
element.addEventListener("click", handler, { once: true, passive: true });

// Cleanup con AbortSignal
const controller = new AbortController();
const { signal } = controller;

element.addEventListener("click", handler, { signal });
// Para remover todos los listeners asociados:
controller.abort();

Evitar onclick en HTML

<!-- ❌ Malo - JavaScript inline -->
<a onclick="guardar()">Guardar</a>
<button onclick="hacerAlgo()">Acción</button>

<!-- ✅ Bueno - Event listener -->
<button type="button" data-action="save">Guardar</button>
<button type="button" data-action="do-something">Acción</button>

<script>
  document.querySelectorAll("[data-action]").forEach((el) => {
    el.addEventListener("click", (e) => {
      // acción...
    });
  });
</script>

Event Delegation

// Un solo listener en el padre en lugar de uno por hijo
document.querySelector(".list")?.addEventListener("click", (event) => {
  const item = event.target.closest(".list__item");
  if (!item) return; // No era un item

  console.log("Item clicked:", item.dataset.id);
});

Pointer Events (unifican mouse + touch)

// Reemplaza mouse + touch events
element.addEventListener("pointerdown", (e) => {
  console.log(e.pointerType); // "mouse" | "touch" | "pen"
  console.log(e.clientX, e.clientY);
});

element.addEventListener("pointerup", handlePointerUp);
element.addEventListener("pointermove", handlePointerMove);

// CSS: touch-action: none si se va a prevenir scroll

Eventos de teclado

// Keyboard accessible
element.addEventListener("keydown", (event) => {
  if (event.key === "Enter" || event.key === " ") {
    event.preventDefault();
    // acción...
  }
});

11. Módulos ES (ESM)

Estructura de archivos

src/
├── js/
│   ├── main.js        # Entry point
│   ├── editor.js      # Lógica del editor
│   └── utils.js       # Funciones helper

Export/Import

// editor.js
export function initEditor() {
  // ...
}

export const EDITOR_CONFIG = {
  maxLength: 10000,
};

// main.js
import { initEditor } from "./editor.js";

document.addEventListener("DOMContentLoaded", () => {
  initEditor();
});

Dynamic import()

// Carga bajo demanda (código splitting)
const module = await import("./editor.js");
module.initEditor();

// Solo importar cuando se necesita
button.addEventListener("click", async () => {
  const { showToast } = await import("./toast.js");
  showToast("Guardado");
});

import.meta

// URL del módulo actual
console.log(import.meta.url);
// "https://ejemplo.com/js/utils.js"

// Metadatos (Vite: import.meta.env, import.meta.glob)
if (import.meta.env?.DEV) {
  console.log("Modo desarrollo");
}

Import Maps

<!-- Controlar resolución de módulos en HTML -->
<script type="importmap">
  {
    "imports": {
      "lodash-es": "/node_modules/lodash-es/lodash.js"
    }
  }
</script>
<script type="module">
  import { debounce } from "lodash-es";
</script>

12. Fetch API

Solicitud básica

async function fetchData(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error("Fetch failed:", error);
    return null;
  }
}

Con headers

async function postData(url, data) {
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer token123",
    },
    body: JSON.stringify(data),
  });
  return response.json();
}

Abort controller (cancelar petición)

const controller = new AbortController();
const signal = controller.signal;

fetch(url, { signal })
  .then((response) => response.json())
  .catch((error) => {
    if (error.name === "AbortError") {
      console.log("Request cancelled");
    }
  });

// Cancelar después de 5 segundos
setTimeout(() => controller.abort(), 5000);

Retry automático

async function fetchWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return await response.json();
    } catch (error) {
      if (i === retries - 1) throw error;
      await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
    }
  }
}

Streaming (respuestas grandes)

// Leer respuesta como stream (sin esperar el body completo)
const response = await fetch("/large-file.json");
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const chunk = decoder.decode(value, { stream: true });
  // Procesar chunk progresivamente
}

// O usando Response.json() con reviver
const data = await response.json({
  reviver: (key, value, context) => {
    if (key === "timestamp") return new Date(value);
    return value;
  },
});

13. Programación Defensiva

Validar inputs

function processData(data) {
  if (!data || typeof data !== "object") {
    console.error("Invalid data");
    return null;
  }
  // procesar...
}

Optional chaining y nullish

// Optional chaining
const value = obj?.nested?.property;
const arr = data?.items?.[0];
const method = obj?.doSomething?.();

// Nullish coalescing
const name = user.name ?? "Anonymous";
const count = items.length ?? 0;

Validar elementos

// Verificar que elemento existe antes de usar
const button = document.querySelector(".btn-submit");
if (button) {
  button.addEventListener("click", handleSubmit);
}

14. Storage

localStorage

// Guardar
localStorage.setItem("user", JSON.stringify({ name: "John" }));

// Leer
const user = JSON.parse(localStorage.getItem("user") || "{}");

// Remover
localStorage.removeItem("user");

// Verificar disponibilidad
const hasStorage = () => {
  try {
    localStorage.setItem("test", "test");
    localStorage.removeItem("test");
    return true;
  } catch (e) {
    return false;
  }
};

sessionStorage

// Similar a localStorage pero se limpia al cerrar pestaña
sessionStorage.setItem("tempData", "value");
const temp = sessionStorage.getItem("tempData");

IndexedDB (wrapper promisificado)

// Wrapper funcional para operaciones comunes
const db = {
  async open(name, version = 1, upgrade) {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(name, version);
      request.onupgradeneeded = upgrade;
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  },

  async get(dbName, storeName, id) {
    const db = await this.open(dbName);
    return new Promise((resolve, reject) => {
      const tx = db.transaction(storeName, "readonly");
      const store = tx.objectStore(storeName);
      const request = store.get(id);
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  },

  async set(dbName, storeName, value) {
    const db = await this.open(dbName);
    return new Promise((resolve, reject) => {
      const tx = db.transaction(storeName, "readwrite");
      const store = tx.objectStore(storeName);
      const request = store.put(value);
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  },
};

// Uso
await db.set("myapp", "items", { id: 1, name: "test" });
const item = await db.get("myapp", "items", 1);

15. Date/Time (Intl API)

Formatear fechas

const date = new Date();

// Formato local
new Intl.DateTimeFormat("es-ES", {
  year: "numeric",
  month: "long",
  day: "numeric",
}).format(date);

// Formato corto
new Intl.DateTimeFormat("en-US", {
  month: "short",
  day: "numeric",
}).format(date);

Formatear números

// Moneda
new Intl.NumberFormat("es-ES", {
  style: "currency",
  currency: "EUR",
}).format(1234.56);

// Porcentaje
new Intl.NumberFormat("en-US", {
  style: "percent",
}).format(0.75);

RelativeTimeFormat

const rtf = new Intl.RelativeTimeFormat("es", { numeric: "auto" });

rtf.format(-1, "day"); // "ayer"
rtf.format(3, "month"); // "dentro de 3 meses"
rtf.format(-5, "minute"); // "hace 5 minutos"

16. Intersection Observer

Detectar cuando un elemento entra en el viewport:

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        entry.target.classList.add("visible");
        // O cargar contenido lazy
      }
    });
  },
  {
    root: null,
    rootMargin: "0px",
    threshold: 0.1,
  },
);

observer.observe(document.querySelector(".lazy-load"));

17. Web Animations API

element.animate(
  [
    { opacity: 0, transform: "translateY(20px)" },
    { opacity: 1, transform: "translateY(0)" },
  ],
  {
    duration: 300,
    easing: "ease-out",
    fill: "forwards",
  },
);

18. APIs de Navegador Modernas

Clipboard API

// Escribir al portapapeles
await navigator.clipboard.writeText("Texto a copiar");

// Leer del portapapeles
const text = await navigator.clipboard.readText();

// Escribir imagen
const blob = new Blob(["<svg>...</svg>"], { type: "image/svg+xml" });
await navigator.clipboard.write([new ClipboardItem({ "image/svg+xml": blob })]);

Web Share API

// Compartir contenido (nativo del SO)
await navigator.share({
  title: "Mi artículo",
  text: "Mira esto",
  url: "https://ejemplo.com",
});

BroadcastChannel (comunicación entre tabs)

// Tab A — enviar
const channel = new BroadcastChannel("app-updates");
channel.postMessage({ type: "LOGOUT", userId: 42 });

// Tab B — recibir
const channel = new BroadcastChannel("app-updates");
channel.addEventListener("message", (event) => {
  if (event.data.type === "LOGOUT") {
    cerrarSesion();
  }
});

WebSocket

const ws = new WebSocket("wss://api.ejemplo.com/ws");

ws.addEventListener("open", () => {
  ws.send(JSON.stringify({ type: "join", room: "chat" }));
});

ws.addEventListener("message", (event) => {
  const data = JSON.parse(event.data);
  mostrarMensaje(data);
});

ws.addEventListener("close", () => {
  console.log("Desconectado");
  // Reconexión automática después de 3s
  setTimeout(conectarWebSocket, 3000);
});

View Transition API

// Transiciones SPA sin librerías
document.addEventListener("click", async (e) => {
  const link = e.target.closest("a[data-transition]");
  if (!link) return;

  e.preventDefault();
  const url = link.href;

  // El navegador captura el estado actual
  const transition = document.startViewTransition(async () => {
    const html = await fetch(url).then((r) => r.text());
    document.body.innerHTML = html;
  });

  await transition.finished;
});

Popover API

// Controlar popover desde JS
const popover = document.getElementById("menu-popover");
popover.showPopover();
popover.hidePopover();
popover.togglePopover();

// Cerrar con hidePopover() desde cualquier evento
popover.addEventListener("beforetoggle", (e) => {
  if (e.newState === "open") {
    console.log("Popover abierto");
  }
});

File System Access API

// Abrir selector de archivos y leer
const [handle] = await window.showOpenFilePicker();
const file = await handle.getFile();
const content = await file.text();

// Guardar archivo
const saveHandle = await window.showSaveFilePicker();
const writable = await saveHandle.createWritable();
await writable.write("Contenido del archivo");
await writable.close();

19. Performance

Evitar Reflows/Repaints

// ❌ Malo - múltiples reflows
element.style.width = "100px";
element.style.height = "100px";
element.style.padding = "10px";

// ✅ Bueno - usar CSS classes o cssText
element.style.cssText = "width: 100px; height: 100px; padding: 10px;";

// ✅ Mejor - usar classList
element.classList.add("active");

requestIdleCallback (tareas no críticas)

// Ejecutar cuando el navegador esté inactivo
requestIdleCallback(
  (deadline) => {
    while (deadline.timeRemaining() > 0 && tasks.length > 0) {
      processTask(tasks.shift());
    }
    if (tasks.length > 0) {
      requestIdleCallback(processTasks); // Continuar después
    }
  },
  { timeout: 2000 },
);

requestAnimationFrame (animaciones JS necesarias)

// Animación fluida con delta time
let start = null;

function animate(timestamp) {
  if (!start) start = timestamp;
  const progress = timestamp - start;

  element.style.transform = `translateX(${Math.min(progress * 0.1, 200)}px)`;

  if (progress < 2000) {
    requestAnimationFrame(animate);
  }
}

requestAnimationFrame(animate);

Debounce

function debounce(fn, delay) {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}

// Uso
const debouncedSearch = debounce(search, 300);
input.addEventListener("input", debouncedSearch);

Throttle

function throttle(fn, limit) {
  let inThrottle;
  return (...args) => {
    if (!inThrottle) {
      fn(...args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

// Uso
const throttledScroll = throttle(handleScroll, 100);
window.addEventListener("scroll", throttledScroll);

20. Prohibiciones

  • NO usar: Vue, Svelte, Angular (sin autorización)
  • NO usar: React (sin cargar skill react)
  • NO usar: TypeScript para vanilla (sin cargar skill typescript)
  • ❌ No usar var
  • ❌ No dejar console.log, debugger en código final
  • ❌ No usar jQuery para selección básica (usar vanilla)
  • ❌ No usar JavaScript para animaciones simples (usar CSS)
  • ❌ No usar onclick en HTML (usar addEventListener)
  • ❌ No usar innerHTML con datos de usuario (riesgo XSS)
  • ❌ No usar JSON.parse(JSON.stringify(obj)) para clonar (usar structuredClone)
  • ❌ No anidar Promise constructors si se puede usar Promise.withResolvers()

21. Dependencias Comunes

JavaScript vanilla típicamente no requiere dependencias. Para proyectos específicos, considerar:

PaqueteUso
date-fnsFechas (si Intl.DateTimeFormat no basta)
idbIndexedDB con promesas (wrapper)

Nota: Con structuredClone(), Object.groupBy(), Set operations e Iterator helpers, lodash ya no es necesario en la mayoría de proyectos vanilla.


22. Referencias

Nota: Para HTML semántico, ver HTML Nota: Para estilos CSS, ver CSS Nota: Para despliegue, ver Deploy


Última actualización: 2026-07

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.