~/nachodiaz.es

$npm run portfolio

loading modules

← Volver al blog

He publicado tres librerías open source (y dos son de Excel)

·5 min de lectura

He publicado tres librerías en npm. Dos atacan el problema de Excel desde frentes distintos — sheet-lite lee, edita y escribe archivos .xlsx sin destruir lo que no tocas, y excel-clipboard-paste convierte lo que pegas desde Excel o Google Sheets en una matriz con la que puedes trabajar. La tercera, nprogress-next, es un reemplazo moderno de NProgress, que no se actualiza desde 2015.

Las tres comparten filosofía: TypeScript, licencia MIT, cero dependencias (o una sola, en el caso de sheet-lite) y CI con presupuesto de tamaño en cada push.

Vamos por partes, de la más importante a la más anecdótica.

sheet-lite: edita un Excel sin romperlo

El problema que me llevó a escribirla es de sobra conocido: cuando abres un Excel, cambias una celda y guardas, la mayoría de librerías reconstruyen el archivo entero. Y en ese proceso se pueden perder por el camino validaciones de datos, formato condicional, imágenes... sin avisar. La primera vez que te pasa, dejas de fiarte de ninguna librería.

sheet-lite hace lo contrario: solo reescribe lo que has cambiado. El resto del paquete OOXML queda byte a byte idéntico. Y si no cambias nada, te devuelve los bytes originales tal cual.

import { Workbook } from 'sheet-lite'

const wb = await Workbook.readFile('./informe.xlsx')
const ws = wb.getWorksheet('Ventas')!

// solo estas celdas se reescriben
ws.getCell('B2').value = 1234.56
ws.getCell('B2').numFmt = '#,##0.00'
ws.getCell('A1').font = { bold: true }
ws.mergeCells('C1:D1')

await wb.writeFile('./informe-editado.xlsx')
// validaciones, formato condicional, imágenes... intactos

La API está inspirada en ExcelJS, así que si vienes de ahí te va a resultar familiar: getCell, addRow, mergeCells, fuentes, rellenos, bordes, alineación, texto enriquecido, hojas nuevas, anchos de columna, filas congeladas...

Los números

Generé un libro de 5000 × 8 celdas con ExcelJS y lo leí con las tres:

LibreríaLectura (mediana)Tamaño publicado (gzip)Deps transitivas
sheet-lite149 ms~30 kB0
ExcelJS192 ms~250 kB9
SheetJS218 ms~136 kB7

El tamaño es engañoso a la hora de comparar: las ~30 kB incluyen la única dependencia directa (fflate, para el ZIP). Para archivos grandes hay un modo aparte, sheet-lite/stream, que lee fila a fila con memoria acotada: una hoja de 100 000 × 8 celdas se procesa con ~10 MB de heap en lugar de los ~673 MB de la lectura en memoria.

También trae adaptadores de navegador (readBlob / toBlob / download) y límites de seguridad por diseño: path traversal, número de entradas del ZIP, tamaño y ratio de compresión.

Como toda v0 honesta, no lo hace todo: no evalúa fórmulas, no crea gráficos ni tablas (los preserva, que es la gracia) y no soporta .xls ni CSV. La lista completa de limitaciones está en el README, a la vista de todos.

excel-clipboard-paste: del portapapeles a datos limpios

Cuando copias un rango en Excel o Google Sheets, el navegador recibe texto separado por tabuladores y saltos de línea. excel-clipboard-paste lo convierte en string[][], es decir, en algo que puedes iterar, validar y guardar:

import { parseClipboard } from 'excel-clipboard-paste'

document.querySelector('textarea')?.addEventListener('paste', (event) => {
  event.preventDefault()
  const rows = parseClipboard(event, { trim: true, rectangular: true })
  // [["Ana", "25"], ["Luis", "31"]]
})

Lo interesante está en los detalles:

  • Opciones encadenadas: trim, dropEmptyRows, dropEmptyColumns, rectangular (iguala las filas cortas) y maxRows.
  • preferHtml: si el origen también manda una <table> en text/html (con colspan/rowspan expandidos), la prefiere; si no hay tabla, cae al texto plano.
  • toObjects(): [["Nombre", "Edad"], ["Ana", "25"]] pasa a objetos usando la primera fila como cabecera.
  • coerceMatrix() es europeo-friendly: convierte "1,234" en 1234 solo cuando los grupos de miles son consistentes; un decimal como "1,5" no acaba convertido en 15.
  • Normaliza los finales de línea de Windows y del Mac clásico, ignora el BOM y no te inventa una fila fantasma por el salto de línea final.

Pesa ~2,8 kB gzip, no tiene dependencias y el repo incluye un playground interactivo para probarlo con pegados reales:

Pegando un rango de Google Sheets en el playground de excel-clipboard-paste

nprogress-next: NProgress por fin en TypeScript

Esta es la más sencilla de contar. NProgress está en millones de proyectos, y su última versión estable (0.2.0) es de 2015. Diez años sin una release, con la API que todos conocemos (start, done, set, inc, configure...).

nprogress-next mantiene esa misma API — migrar es, en la mayoría de los casos, cambiar el import — y la reconstruye con tipos, sin dependencias y en ~3 kB gzip: ESM + CJS + UMD, SSR-safe (Next, Nuxt, Astro), accesible (role="progressbar", aria-valuenow, respeta prefers-reduced-motion) y themable con variables CSS (--np-color, --np-height...).

import NProgress from 'nprogress-next'

NProgress.configure({ showSpinner: false })
NProgress.start()
// ... navegación
NProgress.done()

Si prefieres componentes, hay dos adaptadores oficiales publicados: nprogress-next-react y nprogress-next-vue, ambos con <ProgressBar /> y useProgress().

Tiene documentación propia con ejemplos en vanilla, React, Vue y Astro. Y por supuesto, es un homenaje al trabajo original de Rico Sta. Cruz: misma licencia MIT y crédito preservado en el LICENSE.

Y sí: la barra verde que aparece arriba mientras lees esto es esta librería, siguiendo el scroll del artículo en esta misma web.

Cómo las he construido (spoiler: con agentes de IA)

Estas tres librerías no las he tecleado línea a línea: las he construido con un flujo de desarrollo automatizado con IA. Pero no consiste en pedir "hazme una librería de Excel" y publicar lo que salga. El flujo tiene forma de ingeniería de siempre:

  • Primero la especificación: cada repo nace con su plan por escrito (PLAN.md, ROADMAP.md, docs/STATUS.md) que define qué soporta, qué no y por qué, antes de generar código.
  • Fases pequeñas y verificables: nprogress-next se construyó en seis fases (core, CSS y a11y, adaptadores, tests y CI, docs y publicación). Ninguna fase avanza sin tests en verde.
  • Gates en CI, no en la fe: typecheck, tests, build, npm pack y presupuesto de tamaño — si el bundle crece sin motivo, el build falla.
  • Publicación con provenance: los tres paquetes se publican desde GitHub Actions con atestados de procedencia verificables en npm.
  • Contraste contra la realidad: sheet-lite usa ExcelJS y SheetJS como devDependencies solo para generar fixtures y cruzar resultados en los tests; nunca viajan dentro del paquete publicado.

La IA escribió la mayor parte del código; las decisiones difíciles fueron humanas: qué preservar byte a byte, qué no soportar (y documentarlo en una lista honesta de limitaciones) y qué compromisos son aceptables en una v0. Ese es el verdadero trabajo cuando automatizas: definir bien el problema.

Enlaces

Si trabajas con Excel en la web, las dos primeras te pueden ahorrar horas de dolor. Y si te sirven, una estrella en GitHub ayuda más de lo que parece; si encuentras un caso que no cubren, los issues están abiertos.