Hucre
Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Pure TypeScript, works everywhere.
Install / Use
/learn @productdevbook/HucreREADME
Quick Start
npm install hucre
import { readXlsx, writeXlsx } from "hucre";
// Read an XLSX file
const workbook = await readXlsx(buffer);
console.log(workbook.sheets[0].rows);
// Write an XLSX file
const xlsx = await writeXlsx({
sheets: [
{
name: "Products",
columns: [
{ header: "Name", key: "name", width: 25 },
{ header: "Price", key: "price", width: 12, numFmt: "$#,##0.00" },
{ header: "Stock", key: "stock", width: 10 },
],
data: [
{ name: "Widget", price: 9.99, stock: 142 },
{ name: "Gadget", price: 24.5, stock: 87 },
],
},
],
});
Tree Shaking
Import only what you need:
import { readXlsx, writeXlsx } from "hucre/xlsx"; // XLSX only (~14 KB gzipped)
import { parseCsv, writeCsv } from "hucre/csv"; // CSV only (~2 KB gzipped)
Why hucre?
| | hucre | SheetJS | ExcelJS | read-excel-file | | ----------------- | ------ | ------------- | --------- | --------------- | | Dependencies | 0 | 0* | 12 (CVEs) | 2 | | Bundle (gzip) | ~18 KB | ~300 KB | ~500 KB | ~40 KB | | ESM native | Yes | Partial | No (CJS) | Yes | | TypeScript | Native | Bolted-on | Bolted-on | Yes | | Edge runtime | Yes | No | No | No | | CSP compliant | Yes | Yes | No (eval) | Yes | | npm published | Yes | No (CDN only) | Stale | Yes | | Read + Write | Yes | Yes (Pro $) | Yes | Separate pkgs |
* SheetJS removed itself from npm; must install from CDN tarball.
Features
Reading
import { readXlsx } from "hucre/xlsx";
const wb = await readXlsx(uint8Array, {
sheets: [0, "Products"], // Filter sheets by index or name
readStyles: true, // Parse cell styles
dateSystem: "auto", // Auto-detect 1900/1904
});
for (const sheet of wb.sheets) {
console.log(sheet.name); // "Products"
console.log(sheet.rows); // CellValue[][]
console.log(sheet.merges); // MergeRange[]
}
Supported cell types: strings, numbers, booleans, dates, formulas, rich text, errors, inline strings.
Writing
import { writeXlsx } from "hucre/xlsx";
const buffer = await writeXlsx({
sheets: [
{
name: "Report",
columns: [
{ header: "Date", key: "date", width: 15, numFmt: "yyyy-mm-dd" },
{ header: "Revenue", key: "revenue", width: 15, numFmt: "$#,##0.00" },
{ header: "Active", key: "active", width: 10 },
],
data: [
{ date: new Date("2026-01-15"), revenue: 12500, active: true },
{ date: new Date("2026-01-16"), revenue: 8900, active: false },
],
freezePane: { rows: 1 },
autoFilter: { range: "A1:C3" },
},
],
defaultFont: { name: "Calibri", size: 11 },
});
Features: cell styles, auto column widths, merged cells, freeze/split panes, auto-filter with criteria, data validation, hyperlinks, images (PNG/JPEG/GIF/SVG/WebP), comments, tables, conditional formatting (cellIs/colorScale/dataBar/iconSet), named ranges, print settings, page breaks, sheet protection, workbook protection, rich text, shared/array/dynamic formulas, sparklines, textboxes, background images, number formats, hidden sheets, HTML/Markdown/JSON/TSV export, template engine.
Auto Column Width
const buffer = await writeXlsx({
sheets: [
{
name: "Products",
columns: [
{ header: "Name", key: "name", autoWidth: true },
{ header: "Price", key: "price", autoWidth: true, numFmt: "$#,##0.00" },
{ header: "SKU", key: "sku", autoWidth: true },
],
data: products,
},
],
});
Calculates optimal column widths from cell content — font-aware, handles CJK double-width characters, number formats, min/max constraints.
Data Validation
const buffer = await writeXlsx({
sheets: [
{
name: "Sheet1",
rows: [
["Status", "Quantity"],
["active", 10],
],
dataValidations: [
{
type: "list",
values: ["active", "inactive", "draft"],
range: "A2:A100",
showErrorMessage: true,
errorTitle: "Invalid",
errorMessage: "Pick from the list",
},
{
type: "whole",
operator: "between",
formula1: "0",
formula2: "1000",
range: "B2:B100",
},
],
},
],
});
Hyperlinks
const buffer = await writeXlsx({
sheets: [
{
name: "Links",
rows: [["Visit Google", "Go to Sheet2"]],
cells: new Map([
[
"0,0",
{
value: "Visit Google",
type: "string",
hyperlink: { target: "https://google.com", tooltip: "Open Google" },
},
],
[
"0,1",
{
value: "Go to Sheet2",
type: "string",
hyperlink: { target: "", location: "Sheet2!A1" },
},
],
]),
},
],
});
Streaming
Process large files row-by-row without loading everything into memory:
import { streamXlsxRows, XlsxStreamWriter } from "hucre/xlsx";
// Stream read — async generator yields rows one at a time
for await (const row of streamXlsxRows(buffer)) {
console.log(row.index, row.values);
}
// Stream write — add rows incrementally
const writer = new XlsxStreamWriter({
name: "BigData",
columns: [{ header: "ID" }, { header: "Value" }],
freezePane: { rows: 1 },
});
for (let i = 0; i < 100_000; i++) {
writer.addRow([i + 1, Math.random()]);
}
const buffer = await writer.finish();
ODS (OpenDocument)
import { readOds, writeOds } from "hucre/ods";
const wb = await readOds(buffer);
const ods = await writeOds({ sheets: [{ name: "Sheet1", rows: [["Hello", 42]] }] });
Round-trip Preservation
Open, modify, save — without losing charts, macros, or features hucre doesn't natively handle:
import { openXlsx, saveXlsx } from "hucre/xlsx";
const workbook = await openXlsx(buffer);
workbook.sheets[0].rows[0][0] = "Updated!";
const output = await saveXlsx(workbook); // Charts, VBA, themes preserved
Unified API
Auto-detect format and work with simple helpers:
import { read, write, readObjects, writeObjects } from "hucre";
// Auto-detect XLSX vs ODS
const wb = await read(buffer);
// Quick: file → array of objects
const products = await readObjects<{ name: string; price: number }>(buffer);
// Quick: objects → XLSX
const xlsx = await writeObjects(products, { sheetName: "Products" });
CLI
npx hucre convert input.xlsx output.csv
npx hucre convert input.csv output.xlsx
npx hucre inspect file.xlsx
npx hucre inspect file.xlsx --sheet 0
npx hucre validate data.xlsx --schema schema.json
Sheet Operations
Manipulate sheet data in memory:
import { insertRows, deleteRows, cloneSheet, moveSheet } from "hucre";
insertRows(sheet, 5, 3); // Insert 3 rows at position 5
deleteRows(sheet, 0, 1); // Delete first row
const copy = cloneSheet(sheet, "Copy"); // Deep clone
moveSheet(workbook, 0, 2); // Reorder sheets
HTML & Markdown Export
import { toHtml, toMarkdown } from "hucre";
const html = toHtml(workbook.sheets[0], {
headerRow: true,
styles: true,
classes: true,
});
const md = toMarkdown(workbook.sheets[0]);
// | Name | Price | Stock |
// |--------|-------:|------:|
// | Widget | 9.99 | 142 |
Number Format Renderer
import { formatValue } from "hucre";
formatValue(1234.5, "#,##0.00"); // "1,234.50"
formatValue(0.15, "0%"); // "15%"
formatValue(44197, "yyyy-mm-dd"); // "2021-01-01"
formatValue(1234, "$#,##0"); // "$1,234"
formatValue(0.333, "# ?/?"); // "1/3"
Cell Utilities
import { parseCellRef, cellRef, colToLetter, rangeRef } from "hucre";
parseCellRef("AA15"); // { row: 14, col: 26 }
cellRef(14, 26); // "AA15"
colToLetter(26); // "AA"
rangeRef(0, 0, 9, 3); // "A1:D10"
Builder API
Fluent method-chaining interface:
import { WorkbookBuilder } from "hucre";
const xlsx = await WorkbookBuilder.create()
.addSheet("Products")
.columns([
{ header: "Name", key: "name", autoWidth: true },
{ header: "Price", key: "price", numFmt: "$#,##0.00" },
])
.row(["Widget", 9.99])
.row(["Gadget", 24.5])
.freeze(1)
.done()
.build();
Template Engine
Fill {{placeholders}} in existing XLSX templates:
import { openXlsx, saveXlsx, fillTemplate } from "hucre";
const workbook = await openXlsx(templateBuffer);
fillTemplate(workbook, {
company: "Acme Inc",
date: new Date(),
total: 12500,
});
const output = await saveXlsx(workbook);
JSON Export
import { toJson } from "hucre";
toJson(sheet, { format: "objects" }); // [{Name:"Widget", Price:9.99}, ...]
t
