https://github.com/productdevbook/hucre
Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Pure TypeScript, works everywhere.
https://github.com/productdevbook/hucre
csv csv-parser esm excel ods ods-parser parser spreadsheet streaming tree-shakeable typescript xlsx xlsx-parser xlsx-writer zero-dependency
Last synced: 5 days ago
JSON representation
Zero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Pure TypeScript, works everywhere.
- Host: GitHub
- URL: https://github.com/productdevbook/hucre
- Owner: productdevbook
- License: mit
- Created: 2026-03-24T18:22:34.000Z (15 days ago)
- Default Branch: main
- Last Pushed: 2026-03-27T16:00:47.000Z (12 days ago)
- Last Synced: 2026-03-28T00:57:18.992Z (12 days ago)
- Topics: csv, csv-parser, esm, excel, ods, ods-parser, parser, spreadsheet, streaming, tree-shakeable, typescript, xlsx, xlsx-parser, xlsx-writer, zero-dependency
- Language: TypeScript
- Homepage: https://hucre.productdevbook.com
- Size: 1.18 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
hucre
Zero-dependency spreadsheet engine.
Read & write XLSX, CSV, ODS. Schema validation, streaming, round-trip preservation. Pure TypeScript, works everywhere.
## Quick Start
```sh
npm install hucre
```
```ts
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:
```ts
import { readXlsx, writeXlsx } from "hucre/xlsx"; // XLSX only (~14 KB gzipped)
import { parseCsv, writeCsv } from "hucre/csv"; // CSV only (~2 KB gzipped)
```
## Why hucre?
### vs JavaScript / TypeScript Libraries
| | hucre | SheetJS CE | ExcelJS | xlsx-js-style |
| ----------------------- | --------- | ------------- | --------- | ------------- |
| **Dependencies** | 0 | 0\* | 12 (CVEs) | 0\* |
| **Bundle (gzip)** | ~18 KB | ~300 KB | ~500 KB | ~300 KB |
| **ESM native** | Yes | Partial | No (CJS) | Partial |
| **TypeScript** | Native | Bolted-on | Bolted-on | Bolted-on |
| **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 | Yes |
| **Styling** | Yes | No (Pro $) | Yes | Yes |
| **Cond. formatting** | Yes (all) | No (Pro $) | Partial | No |
| **Stream read + write** | Yes | CSV only | Yes | CSV only |
| **ODS support** | Yes | Yes | No | Yes |
| **Round-trip** | Yes | Partial | Partial | Partial |
| **Sparklines** | Yes | No | No | No |
| **Tables** | Yes | Yes | Yes | Yes |
| **Images** | Yes | No (Pro $) | Yes | No |
\* SheetJS removed itself from npm; must install from CDN tarball.
### vs Libraries in Other Languages
| | hucre (TS) | openpyxl (Py) | XlsxWriter (Py) | rust_xlsxwriter | Apache POI (Java) |
| --------------------- | ------------ | ------------- | --------------- | --------------- | ----------------- |
| **Read XLSX** | Yes | Yes | No | No | Yes |
| **Write XLSX** | Yes | Yes | Yes | Yes | Yes |
| **Streaming** | Read+Write | Write-only | No | const_memory | SXSSF (write) |
| **Charts** | Round-trip | 15+ types | 9 types | 12+ types | Limited |
| **Pivot tables** | No | Read-only | No | No | Limited |
| **Cond. formatting** | Yes (all) | Yes | Yes | Yes | Yes |
| **Sparklines** | Yes | No | Yes | Yes | No |
| **Formula eval** | No | No | No | No | Yes |
| **Multi-format** | XLSX/ODS/CSV | XLSX only | XLSX only | XLSX only | XLS/XLSX |
| **Zero dependencies** | Yes | lxml optional | No | Yes | No |
## Features
### Reading
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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:
```ts
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)
```ts
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:
```ts
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:
```ts
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
```bash
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:
```ts
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
```ts
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
```ts
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
```ts
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:
```ts
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:
```ts
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
```ts
import { toJson } from "hucre";
toJson(sheet, { format: "objects" }); // [{Name:"Widget", Price:9.99}, ...]
toJson(sheet, { format: "columns" }); // {Name:["Widget"], Price:[9.99]}
toJson(sheet, { format: "arrays" }); // {headers:[...], data:[[...]]}
```
### CSV
```ts
import { parseCsv, parseCsvObjects, writeCsv, detectDelimiter } from "hucre/csv";
// Parse — auto-detects delimiter, handles RFC 4180 edge cases
const rows = parseCsv(csvString, { typeInference: true });
// Parse with headers — returns typed objects
const { data, headers } = parseCsvObjects(csvString, { header: true });
// Write
const csv = writeCsv(rows, { delimiter: ";", bom: true });
// Detect delimiter
detectDelimiter(csvString); // "," or ";" or "\t" or "|"
```
### Schema Validation
Validate imported data with type coercion, pattern matching, and error collection:
```ts
import { validateWithSchema } from "hucre";
import { parseCsv } from "hucre/csv";
const rows = parseCsv(csvString);
const result = validateWithSchema(
rows,
{
"Product Name": { type: "string", required: true },
Price: { type: "number", required: true, min: 0 },
SKU: { type: "string", pattern: /^[A-Z]{3}-\d{4}$/ },
Stock: { type: "integer", min: 0, default: 0 },
Status: { type: "string", enum: ["active", "inactive", "draft"] },
},
{ headerRow: 1 },
);
console.log(result.data); // Validated & coerced objects
console.log(result.errors); // [{ row: 3, field: "Price", message: "...", value: "abc" }]
```
Schema field options:
| Option | Type | Description |
| ------------- | ---------------------------------------------------------- | --------------------------------------- |
| `type` | `"string" \| "number" \| "integer" \| "boolean" \| "date"` | Target type (with coercion) |
| `required` | `boolean` | Reject null/empty values |
| `pattern` | `RegExp` | Regex validation (strings) |
| `min` | `number` | Min value (numbers) or length (strings) |
| `max` | `number` | Max value (numbers) or length (strings) |
| `enum` | `unknown[]` | Allowed values |
| `default` | `unknown` | Default for null/empty |
| `validate` | `(v) => boolean \| string` | Custom validator |
| `transform` | `(v) => unknown` | Post-validation transform |
| `column` | `string` | Column header name |
| `columnIndex` | `number` | Column index (0-based) |
### Date Utilities
Timezone-safe Excel date serial number conversion:
```ts
import { serialToDate, dateToSerial, isDateFormat, formatDate } from "hucre";
serialToDate(44197); // 2021-01-01T00:00:00.000Z
dateToSerial(new Date("2021-01-01")); // 44197
isDateFormat("yyyy-mm-dd"); // true
isDateFormat("#,##0.00"); // false
formatDate(new Date(), "yyyy-mm-dd"); // "2026-03-24"
```
Handles the Lotus 1-2-3 bug (serial 60), 1900/1904 date systems, and time fractions correctly.
## Platform Support
hucre works everywhere — no Node.js APIs (`fs`, `crypto`, `Buffer`) in core.
| Runtime | Status |
| --------------------- | ------------ |
| Node.js 18+ | Full support |
| Deno | Full support |
| Bun | Full support |
| Modern browsers | Full support |
| Cloudflare Workers | Full support |
| Vercel Edge Functions | Full support |
| Web Workers | Full support |
## Architecture
```
hucre (~37 KB gzipped)
├── zip/ Zero-dep DEFLATE/inflate + ZIP read/write
├── xml/ SAX parser + XML writer (CSP-compliant, no eval)
├── xlsx/
│ ├── reader Shared strings, styles, worksheets, relationships
│ ├── writer Styles, shared strings, drawing, tables, comments
│ ├── roundtrip Open → modify → save with preservation
│ ├── stream-* Streaming reader (AsyncGenerator) + writer
│ └── auto-width Font-aware column width calculation
├── ods/ OpenDocument Spreadsheet read/write
├── csv/ RFC 4180 parser/writer + streaming
├── export/ HTML, Markdown, JSON, TSV output + HTML import
├── hucre Unified read/write API, format auto-detect
├── builder Fluent WorkbookBuilder / SheetBuilder API
├── template {{placeholder}} template engine
├── sheet-ops Insert/delete/move/sort/find/replace, clone, copy
├── cell-utils parseCellRef, colToLetter, parseRange, isInRange
├── image imageFromBase64 utility
├── worker Web Worker serialization helpers
├── _date Timezone-safe serial ↔ Date, Lotus bug, 1900/1904
├── _format Number format renderer (locale-aware)
├── _schema Schema validation, type coercion, error collection
└── cli Convert, inspect, validate (citty + consola)
```
Zero dependencies. Pure TypeScript. The ZIP engine uses `CompressionStream`/`DecompressionStream` Web APIs with a pure TS fallback.
## API Reference
### High-level
| Function | Description |
| ------------------------------ | ------------------------------------------------- |
| `read(input, options?)` | Auto-detect format (XLSX/ODS), returns `Workbook` |
| `write(options)` | Write XLSX or ODS (via `format` option) |
| `readObjects(input, options?)` | File → array of objects (first row = headers) |
| `writeObjects(data, options?)` | Objects → XLSX/ODS |
### XLSX
| Function | Description |
| --------------------------------- | --------------------------------------------- |
| `readXlsx(input, options?)` | Parse XLSX from `Uint8Array \| ArrayBuffer` |
| `writeXlsx(options)` | Generate XLSX, returns `Uint8Array` |
| `openXlsx(input, options?)` | Open for round-trip (preserves unknown parts) |
| `saveXlsx(workbook)` | Save round-trip workbook back to XLSX |
| `streamXlsxRows(input, options?)` | AsyncGenerator yielding rows one at a time |
| `XlsxStreamWriter` | Class for incremental row-by-row XLSX writing |
### ODS
| Function | Description |
| -------------------------- | ------------------------------------ |
| `readOds(input, options?)` | Parse ODS (OpenDocument Spreadsheet) |
| `writeOds(options)` | Generate ODS |
| `streamOdsRows(input)` | AsyncGenerator yielding ODS rows |
### CSV
| Function | Description |
| ---------------------------------- | -------------------------------------------- |
| `parseCsv(input, options?)` | Parse CSV string → `CellValue[][]` |
| `parseCsvObjects(input, options?)` | Parse CSV with headers → `{ data, headers }` |
| `writeCsv(rows, options?)` | Write `CellValue[][]` → CSV string |
| `writeCsvObjects(data, options?)` | Write objects → CSV string |
| `detectDelimiter(input)` | Auto-detect delimiter character |
| `streamCsvRows(input, options?)` | Generator yielding CSV rows |
| `CsvStreamWriter` | Class for incremental CSV writing |
| `writeTsv(rows, options?)` | Write TSV (tab-separated) |
| `fetchCsv(url, options?)` | Fetch and parse CSV from URL |
### Sheet Operations
| Function | Description |
| --------------------------------------- | ------------------------------- |
| `insertRows(sheet, index, count)` | Insert rows, shift down |
| `deleteRows(sheet, index, count)` | Delete rows, shift up |
| `insertColumns(sheet, index, count)` | Insert columns, shift right |
| `deleteColumns(sheet, index, count)` | Delete columns, shift left |
| `moveRows(sheet, from, count, to)` | Move rows |
| `cloneSheet(sheet, name)` | Deep clone a sheet |
| `copySheetToWorkbook(sheet, wb, name?)` | Copy sheet between workbooks |
| `copyRange(sheet, source, target)` | Copy cell range within sheet |
| `moveSheet(wb, from, to)` | Reorder sheets |
| `removeSheet(wb, index)` | Remove a sheet |
| `sortRows(sheet, col, order?)` | Sort rows by column |
| `findCells(sheet, predicate)` | Find cells by value or function |
| `replaceCells(sheet, find, replace)` | Find and replace values |
### Export
| Function | Description |
| ----------------------------- | ------------------------------------------------ |
| `toHtml(sheet, options?)` | HTML `` with styles, a11y, dark/light CSS |
| `toMarkdown(sheet, options?)` | Markdown table with auto-alignment |
| `toJson(sheet, options?)` | JSON (objects, arrays, or columns format) |
| `fromHtml(html, options?)` | Parse HTML table string → Sheet |
### Builder
| Function | Description |
| ------------------------------ | --------------------------------------- |
| `WorkbookBuilder.create()` | Fluent API for building workbooks |
| `fillTemplate(workbook, data)` | Replace `{{placeholders}}` in templates |
### Formatting & Utilities
| Function | Description |
| -------------------------------------------- | ---------------------------------------- |
| `formatValue(value, numFmt, options?)` | Apply Excel number format (locale-aware) |
| `validateWithSchema(rows, schema, options?)` | Validate & coerce data with schema |
| `serialToDate(serial, is1904?)` | Excel serial → Date (UTC) |
| `dateToSerial(date, is1904?)` | Date → Excel serial |
| `isDateFormat(numFmt)` | Check if format string is date |
| `formatDate(date, format)` | Format Date with Excel format string |
| `parseCellRef(ref)` | "AA15" → `{ row: 14, col: 26 }` |
| `cellRef(row, col)` | `(14, 26)` → "AA15" |
| `colToLetter(col)` | `26` → "AA" |
| `rangeRef(r1, c1, r2, c2)` | `(0,0,9,3)` → "A1:D10" |
### Web Worker Helpers
| Function | Description |
| --------------------------- | -------------------------------------------------------------------- |
| `serializeWorkbook(wb)` | Convert Workbook for `postMessage` (Maps → objects, Dates → strings) |
| `deserializeWorkbook(data)` | Restore Workbook from serialized form |
| `WORKER_SAFE_FUNCTIONS` | List of all hucre functions safe for Web Workers (all of them) |
## Development
```sh
pnpm install
pnpm dev # vitest watch
pnpm test # lint + typecheck + test
pnpm build # obuild (minified, tree-shaken)
pnpm lint:fix # oxlint + oxfmt
pnpm typecheck # tsgo
```
## Contributing
Contributions are welcome! Please [open an issue](https://github.com/productdevbook/hucre/issues) or submit a PR.
127 of 135 tracked features are implemented. See the [issue tracker](https://github.com/productdevbook/hucre/issues) for the roadmap.
### Roadmap
**Report Builder** — Enhanced `writeObjects` with column mapping, formula helpers, conditional style rules, summary rows, style presets, column groups, sub-row expansion, Excel table auto-generation, streaming object writes.
**Upcoming Engine Features:**
- Chart creation (bar, line, pie, scatter, area + subtypes)
- XLS BIFF8 read (legacy Excel 97-2003)
- XLSB binary format read
- Formula evaluation engine
- File encryption/decryption (AES-256, MS-OFFCRYPTO)
- Pivot table creation
- Threaded comments (Excel 365+)
- Checkboxes (Excel 2024+)
- VBA/macro injection
- Slicers & timeline filters
- R1C1 notation support
- Accessibility helpers (WCAG 2.1 AA)
## License
[MIT](./LICENSE) — Made by [productdevbook](https://github.com/productdevbook)