npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

xlsx-handlebars

v0.2.2

Published

A Rust library for processing XLSX files with Handlebars templates, supporting WASM, Node.js, Deno, and browsers

Readme

xlsx-handlebars

Crates.io Documentation License

English | 中文文档 | Demo

A Rust library for processing XLSX files with Handlebars templates, supporting multiple platforms:

  • 🦀 Rust Native
  • 🌐 WebAssembly (WASM)
  • 📦 npm Package
  • 🟢 Node.js
  • 🦕 Deno
  • 🌍 Browser
  • 📋 JSR (JavaScript Registry)

Features

  • High Performance: Renders 100,000 rows in just 2.12 seconds (~47,000 rows/sec) - 14-28x faster than Python, 7-14x faster than JavaScript
  • Smart Merge: Automatically handles Handlebars syntax split by XML tags
  • XLSX Validation: Built-in file format validation to ensure valid input files
  • Handlebars Support: Full template engine with variables, conditions, loops, and Helper functions
  • Cross-Platform: Rust native + WASM support for multiple runtimes
  • TypeScript: Complete type definitions and IntelliSense
  • Zero Dependencies: WASM binary with no external dependencies

Installation

Rust

cargo add xlsx-handlebars

npm

npm install xlsx-handlebars

Deno

import init, { render_template } from "jsr:@sail/xlsx-handlebars";

Usage Examples

Rust

use xlsx_handlebars::render_template;
use serde_json::json;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Read XLSX template file
    let template_bytes = std::fs::read("template.xlsx")?;
    
    // Prepare data
    let data = json!({
        "name": "John Doe",
        "company": "ABC Tech Inc.",
        "position": "Software Engineer",
        "projects": [
            {"name": "Project A", "status": "Completed"},
            {"name": "Project B", "status": "In Progress"}
        ],
        "has_bonus": true,
        "bonus_amount": 5000
    });
    
    // Render template
    let result = render_template(template_bytes, &data)?;
    
    // Save result
    std::fs::write("output.xlsx", result)?;
    
    Ok(())
}

JavaScript/TypeScript (Node.js)

import init, { render_template } from 'xlsx-handlebars';
import fs from 'fs';

async function processTemplate() {
    // Initialize WASM module
    await init();
    
    // Read template file
    const templateBytes = fs.readFileSync('template.xlsx');
    
    // Prepare data
    const data = {
        name: "Jane Smith",
        company: "XYZ Technology Ltd.",
        position: "Senior Developer",
        projects: [
            { name: "E-commerce Platform", status: "Completed" },
            { name: "Mobile App", status: "In Development" }
        ],
        has_bonus: true,
        bonus_amount: 8000
    };
    
    // Render template
    const result = render_template(templateBytes, JSON.stringify(data));
    
    // Save result
    fs.writeFileSync('output.xlsx', new Uint8Array(result));
}

processTemplate().catch(console.error);

Deno

import init, { render_template } from "https://deno.land/x/xlsx_handlebars/mod.ts";

async function processTemplate() {
    // Initialize WASM module
    await init();
    
    // Read template file
    const templateBytes = await Deno.readFile("template.xlsx");
    
    // Prepare data
    const data = {
        name: "Alice Johnson",
        department: "R&D",
        projects: [
            { name: "AI Customer Service", status: "Live" },
            { name: "Data Visualization Platform", status: "In Development" }
        ]
    };
    
    // Render template
    const result = render_template(templateBytes, JSON.stringify(data));
    
    // Save result
    await Deno.writeFile("output.xlsx", new Uint8Array(result));
}

if (import.meta.main) {
    await processTemplate();
}

Browser

<!DOCTYPE html>
<html>
<head>
    <title>XLSX Handlebars Example</title>
</head>
<body>
    <input type="file" id="fileInput" accept=".xlsx">
    <button onclick="processFile()">Process Template</button>
    
    <script type="module">
        import init, { render_template } from './pkg/xlsx_handlebars.js';
        
        // Initialize WASM
        await init();
        
        window.processFile = async function() {
            const fileInput = document.getElementById('fileInput');
            const file = fileInput.files[0];
            
            if (!file) return;
            
            const arrayBuffer = await file.arrayBuffer();
            const templateBytes = new Uint8Array(arrayBuffer);
            
            const data = {
                name: "John Doe",
                company: "Example Company"
            };
            
            try {
                const result = render_template(templateBytes, JSON.stringify(data));
                
                // Download result
                const blob = new Blob([new Uint8Array(result)], {
                    type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
                });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = 'processed.xlsx';
                a.click();
            } catch (error) {
                console.error('Processing failed:', error);
            }
        };
    </script>
</body>
</html>

Template Syntax

Basic Variable Substitution

Employee Name: {{name}}
Company: {{company}}
Position: {{position}}

Conditional Rendering

{{#if has_bonus}}
Bonus: ${{bonus_amount}}
{{else}}
No Bonus
{{/if}}

{{#unless is_intern}}
Full-time Employee
{{/unless}}

Loop Rendering

Project Experience:
{{#each projects}}
- {{name}}: {{description}} ({{status}})
{{/each}}

Skills:
{{#each skills}}
{{@index}}. {{this}}
{{/each}}

Helper Functions

Built-in Helper functions:

<!-- Basic helpers -->
{{upper name}}           <!-- Convert to uppercase -->
{{lower company}}        <!-- Convert to lowercase -->
{{len projects}}         <!-- Array length -->
{{#if (eq status "completed")}}Completed{{/if}}    <!-- Equality comparison -->
{{#if (gt score 90)}}Excellent{{/if}}              <!-- Greater than comparison -->
{{#if (lt age 30)}}Young{{/if}}                    <!-- Less than comparison -->

<!-- String concatenation -->
{{concat "Hello" " " "World"}}                     <!-- String concatenation -->
{{concat "Total: " count}}                         <!-- Mix strings and variables -->

<!-- Excel-specific helpers -->
{{num employee.salary}}                            <!-- Mark cell as number type -->
{{formula "=SUM(A1:B1)"}}                         <!-- Static Excel formula -->
{{formula (concat "=SUM(" (_c) "1:" (_c) "10)")}} <!-- Dynamic formula with current column -->
{{mergeCell "C4:D5"}}                             <!-- Merge cells C4 to D5 -->
{{img logo.data 100 100}}                          <!-- Insert image with width and height -->

<!-- Column name conversion helpers -->
{{toColumnName "A" 5}}                             <!-- A + 5 offset = F -->
{{toColumnName (_c) 3}}                            <!-- Current column + 3 offset -->
{{toColumnIndex "AA"}}                             <!-- AA column index = 27 -->

Excel Formula Helpers

Static Formula:

<!-- In Excel cell -->
{{formula "=SUM(A1:B1)"}}
{{formula "=AVERAGE(C2:C10)"}}
{{formula "=IF(D1>100,\"High\",\"Low\")"}}

Dynamic Formula with concat:

<!-- Dynamic row reference -->
{{formula (concat "=A" (_r) "*B" (_r))}}

<!-- Dynamic column reference -->
{{formula (concat "=SUM(" (_c) "2:" (_c) "10)")}}

<!-- Complex dynamic formula -->
{{formula (concat "=IF(" (_cr) ">100,\"High\",\"Low\")")}}

Available position helpers:

  • (_c) - Current column letter (A, B, C, ...)
  • (_r) - Current row number (1, 2, 3, ...)
  • (_cr) - Current cell reference (A1, B2, C3, ...)

Column Name Conversion Helpers

toColumnName - Convert column name or index to a new column name with optional offset:

<!-- Basic usage: offset from specified column -->
{{toColumnName "A" 0}}     <!-- A (no offset) -->
{{toColumnName "A" 5}}     <!-- F (A + 5) -->
{{toColumnName "Z" 1}}     <!-- AA (Z + 1) -->

<!-- Use with current column -->
{{toColumnName (_c) 3}}    <!-- Current column + 3 offset -->

<!-- Application in dynamic formulas -->
{{formula (concat "=SUM(" (_c) "1:" (toColumnName (_c) 3) "1)")}}
<!-- Example: If current column is B, generates formula =SUM(B1:E1) -->

toColumnIndex - Convert column name to column index (1-based):

{{toColumnIndex "A"}}      <!-- 1 -->
{{toColumnIndex "Z"}}      <!-- 26 -->
{{toColumnIndex "AA"}}     <!-- 27 -->
{{toColumnIndex "AB"}}     <!-- 28 -->

Merge Cells Helper

mergeCell - Mark cell ranges that need to be merged:

<!-- Static cell merging -->
{{mergeCell "C4:D5"}}      <!-- Merge C4 to D5 region -->
{{mergeCell "F4:G4"}}      <!-- Merge F4 to G4 region -->

<!-- Dynamic cell merging: from current position -->
{{mergeCell (concat (_c) (_r) ":" (toColumnName (_c) 3) (_r))}}
<!-- Example: If current is B5, merges B5:E5 (4 columns to the right) -->

<!-- Dynamic cell merging: spanning rows and columns -->
{{mergeCell (concat (_c) (_r) ":" (toColumnName (_c) 2) (add (_r) 2))}}
<!-- Example: If current is C3, merges C3:E5 (3×3 region) -->

<!-- Dynamic merging in loops -->
{{#each sections}}
  {{mergeCell (concat "A" (add @index 2) ":D" (add @index 2))}}
  <!-- Merge columns A-D for each section row -->
{{/each}}

Notes:

  • mergeCell produces no output, only collects merge information
  • Merge range format must be StartCell:EndCell (e.g., "A1:B2")
  • Duplicate merge ranges are automatically deduplicated
  • Merge information is automatically added to the Excel file after rendering

Hyperlink Helper

hyperlink - Add hyperlinks to Excel cells:

<!-- Basic usage: link to another worksheet -->
{{hyperlink (_cr) "Sheet2!A1" "View Details"}}

<!-- Link to external URL (requires pre-configuration in template) -->
{{hyperlink (_cr) "https://example.com" "Visit Website"}}

<!-- Dynamic links -->
{{#each items}}
  {{hyperlink (_cr) (concat "Details!" name) name}}
{{/each}}

Parameters:

  • First parameter: Cell reference, typically use (_cr) for current cell
  • Second parameter: Link target (worksheet reference or URL)
  • Third parameter: Display text (optional)

Notes:

  • hyperlink produces no output, only collects hyperlink information
  • Hyperlinks are automatically added to the Excel file after rendering
  • Supports internal worksheet references (e.g., "Sheet2!A1")
  • External links require pre-configured relationships in the template Excel file

Number Type Helper

Use {{num value}} to ensure a cell is treated as a number in Excel:

<!-- Without num: treated as text -->
{{employee.salary}}

<!-- With num: treated as number -->
{{num employee.salary}}

This is especially useful when:

  • The value might be a string but should be treated as a number
  • You want to ensure proper number formatting in Excel
  • You need the value to work in formulas

Image Insertion Helper

img - Insert base64-encoded images into Excel:

<!-- Basic usage: insert image with original dimensions -->
{{img logo.data}}

<!-- Specify width and height (in pixels) -->
{{img photo.data 150 200}}

<!-- Use dimensions from data -->
{{img image.data image.width image.height}}

Features:

  • ✅ Supports common image formats: PNG, JPEG, WebP, BMP, TIFF, GIF
  • ✅ Auto-detects actual image dimensions
  • ✅ Optional width and height specification (in pixels)
  • ✅ Image positioned at current cell location
  • ✅ Images are not constrained by cell size, maintain aspect ratio
  • ✅ Supports multiple images in the same sheet
  • ✅ Supports images in multiple sheets
  • ✅ Uses UUID to avoid ID conflicts

Complete Example:

// Prepare image data in JavaScript
import fs from 'fs';

const imageBuffer = fs.readFileSync('logo.png');
const base64Image = imageBuffer.toString('base64');

const data = {
  company: {
    logo: base64Image,
    name: "Tech Company"
  },
  products: [
    {
      name: "Product A",
      photo: base64Image,
      width: 120,
      height: 120
    },
    {
      name: "Product B", 
      photo: base64Image,
      width: 100,
      height: 100
    }
  ]
};

// Use in template
<!-- Excel template example -->
Company Logo: {{img company.logo 100 50}}

Product List:
{{#each products}}
Product Name: {{name}}
Image: {{img photo width height}}
{{/each}}

Usage Tips:

  • If only width is specified, height scales proportionally
  • If only height is specified, width scales proportionally
  • If neither is specified, original image dimensions are used
  • Image will be placed at the cell location where {{img}} is called
  • base64 data should not include the data:image/png;base64, prefix, just the pure base64 string

Worksheet Management Helpers

deleteCurrentSheet - Delete the current worksheet being rendered:

<!-- Basic usage -->
{{deleteCurrentSheet}}

<!-- Conditional deletion -->
{{#if shouldDelete}}
  {{deleteCurrentSheet}}
{{/if}}

<!-- Delete inactive sheets -->
{{#unless isActive}}
  {{deleteCurrentSheet}}
{{/unless}}

Features:

  • ✅ Removes worksheet and its relationships from workbook
  • ✅ Cleans up related files (rels, content types)
  • ✅ Drawing files are preserved (safe approach)
  • ✅ Cannot delete the last worksheet (Excel requirement)
  • ✅ Delayed execution after all rendering completes

setCurrentSheetName - Rename the current worksheet:

<!-- Static name -->
{{setCurrentSheetName "Sales Report"}}

<!-- Dynamic name -->
{{setCurrentSheetName (concat department.name " - " year)}}

<!-- Loop-based naming -->
{{#each departments}}
  {{setCurrentSheetName (concat "Department " @index " - " name)}}
{{/each}}

Features:

  • ✅ Auto-filters invalid characters: \ / ? * [ ]
  • ✅ Auto-limits length to 31 characters
  • ✅ Auto-handles duplicate names with numeric suffixes
  • ✅ Supports dynamic name generation

hideCurrentSheet - Hide the current worksheet:

<!-- Normal hide (user can unhide via right-click) -->
{{hideCurrentSheet}}
{{hideCurrentSheet "hidden"}}

<!-- Very hidden (requires VBA to unhide) -->
{{hideCurrentSheet "veryHidden"}}

<!-- Conditional hiding -->
{{#unless (eq userRole "admin")}}
  {{hideCurrentSheet "veryHidden"}}
{{/unless}}

Hide Levels:

  • hidden - Normal hide, users can unhide via Excel's right-click menu
  • veryHidden - Super hide, requires VBA or property editor to unhide

Features:

  • ✅ Cannot hide all worksheets (Excel requires at least one visible)
  • ✅ Two hiding levels: normal and super hidden
  • ✅ Useful for permission control and sensitive data

Common Use Cases:

<!-- Multi-language reports: delete unused language sheets -->
{{#if (ne language "en-US")}}
  {{deleteCurrentSheet}}
{{/if}}

<!-- Dynamic department reports: rename sheets by department -->
{{setCurrentSheetName (concat department.name " Report")}}

<!-- Permission control: hide admin sheets from regular users -->
{{#unless (eq userRole "admin")}}
  {{hideCurrentSheet "veryHidden"}}
{{/unless}}

<!-- Conditional workflow: delete, rename, or hide based on status -->
{{#if (eq status "inactive")}}
  {{deleteCurrentSheet}}
{{else}}
  {{setCurrentSheetName (concat "Active - " name)}}
  {{#if isInternal}}
    {{hideCurrentSheet}}
  {{/if}}
{{/if}}

Complex Example

=== Employee Report ===

Basic Information:
Name: {{employee.name}}
Department: {{employee.department}}
Position: {{employee.position}}
Hire Date: {{employee.hire_date}}

{{#if employee.has_bonus}}
💰 Bonus: ${{employee.bonus_amount}}
{{/if}}

Project Experience (Total {{len projects}}):
{{#each projects}}
{{@index}}. {{name}}
   Description: {{description}}
   Status: {{status}}
   Team Size: {{team_size}} people
   
{{/each}}

Skills Assessment:
{{#each skills}}
- {{name}}: {{level}}/10 ({{years}} years of experience)
{{/each}}

To remove an entire row in a table, simply add to any cell:
{{removeRow}}


{{#if (gt performance.score 90)}}
🎉 Performance Rating: Excellent
{{else if (gt performance.score 80)}}
👍 Performance Rating: Good
{{else}}
📈 Performance Rating: Needs Improvement
{{/if}}

Build and Development

Build WASM Package

# Build all targets
npm run build

# Or build separately
npm run build:web    # Browser version
npm run build:npm    # Node.js version 
npm run build:jsr    # Deno version

Run Examples

# Rust example
cargo run --example rust_example

# Node.js example
node examples/node_example.js

# Deno example  
deno run --allow-read --allow-write examples/deno_example.ts

# Browser example
cd tests/npm_test
node serve.js
# Then open http://localhost:8080 in your browser
# Select examples/template.xlsx file to test

Utility Functions

xlsx-handlebars provides a set of utility functions to help you work more efficiently with Excel operations.

Excel Column Name Conversion

Convert between Excel column names and column indices.

use xlsx_handlebars::{to_column_name, to_column_index};

// Column name increment
assert_eq!(to_column_name("A", 0), "A");
assert_eq!(to_column_name("A", 1), "B");
assert_eq!(to_column_name("Z", 1), "AA");
assert_eq!(to_column_name("AA", 1), "AB");

// Column name to index (1-based)
assert_eq!(to_column_index("A"), 1);
assert_eq!(to_column_index("Z"), 26);
assert_eq!(to_column_index("AA"), 27);
assert_eq!(to_column_index("BA"), 53);

JavaScript/TypeScript Example:

import { wasm_to_column_name, wasm_to_column_index } from 'xlsx-handlebars';

// Column name increment
console.log(wasm_to_column_name("A", 1));  // "B"
console.log(wasm_to_column_name("Z", 1));  // "AA"

// Column name to index
console.log(wasm_to_column_index("AA"));   // 27
console.log(wasm_to_column_index("BA"));   // 53

Excel Date Conversion

Convert between Unix timestamps and Excel date serial numbers. Excel uses serial numbers starting from 1900-01-01 to represent dates.

use xlsx_handlebars::{timestamp_to_excel_date, excel_date_to_timestamp};

// Timestamp to Excel date
let timestamp = 1704067200000i64;  // 2024-01-01 00:00:00 UTC
let excel_date = timestamp_to_excel_date(timestamp);
println!("Excel date serial number: {}", excel_date);  // 45294.0

// Excel date to timestamp
if let Some(ts) = excel_date_to_timestamp(45294.0) {
    println!("Timestamp: {}", ts);  // 1704067200000
}

JavaScript/TypeScript Example:

import { 
    wasm_timestamp_to_excel_date, 
    wasm_excel_date_to_timestamp 
} from 'xlsx-handlebars';

// Date to Excel serial number
const date = new Date('2024-01-01T00:00:00Z');
const excelDate = wasm_timestamp_to_excel_date(date.getTime());
console.log('Excel date:', excelDate);  // 45294.0

// Excel serial number to date
const timestamp = wasm_excel_date_to_timestamp(45294.0);
if (timestamp !== null) {
    const convertedDate = new Date(timestamp);
    console.log('Date:', convertedDate.toISOString());
}

Common Use Cases:

// Dynamically generate cell references
let start_col = "B";
let num_cols = 5;
for i in 0..num_cols {
    let col_name = to_column_name(start_col, i);
    let col_index = to_column_index(&col_name);
    println!("Column {}: name={}, index={}", i, col_name, col_index);
}
// Include dates in template data
use serde_json::json;

let date_timestamp = 1704067200000i64;  // 2024-01-01
let excel_date = timestamp_to_excel_date(date_timestamp);

let data = json!({
    "report_date": excel_date,
    "employee": {
        "name": "John Doe",
        "hire_date": timestamp_to_excel_date(1609459200000i64)  // 2021-01-01
    }
});

These utility functions help you:

  • ✅ Dynamically generate cell references and formulas
  • ✅ Handle Excel date formats
  • ✅ Avoid loading heavy external libraries
  • ✅ Support both Rust and JavaScript/TypeScript

Technical Features

Performance and Compatibility

Blazing Fast Performance ⚡

xlsx-handlebars delivers industry-leading performance powered by Rust:

| Data Size | Processing Time | Throughput | |-----------|----------------|------------| | 1,000 rows | ~0.02s | Real-time generation | | 10,000 rows | ~0.21s | Online exports | | 100,000 rows | ~2.12s | Batch processing | | 1,000,000 rows | ~21s | Big data reports |

Performance Comparison (100,000 rows):

| Technology | Time | Speed vs xlsx-handlebars | |-----------|------|-------------------------| | xlsx-handlebars (Rust) | 2.12s | 1x (baseline) ⭐ | | Python (openpyxl) | 30-60s | 14-28x slower | | JavaScript (xlsx.js) | 15-30s | 7-14x slower | | Java (Apache POI) | 8-15s | 3-7x slower | | C# (EPPlus) | 5-10s | 2-4x slower |

Why So Fast?

  • 🦀 Rust's Zero-Cost Abstractions: Compile-time optimizations with no runtime overhead
  • 🔄 Streaming Architecture: Process ZIP entries directly in memory without file I/O
  • Event-Driven XML Parsing: Uses quick-xml for efficient parsing without building full DOM trees
  • 🎯 Single-Pass Rendering: All template substitutions in one iteration

Compatibility

  • Zero-Copy: Efficient memory management between Rust and WASM
  • Streaming: Suitable for processing large XLSX files
  • Cross-Platform: Supports Windows, macOS, Linux, Web
  • Modern Browsers: Supports all modern browsers with WASM support

License

This project is licensed under the MIT License - see the LICENSE-MIT file for details.

Support


Support this project with a donation via Alipay:

Support this project with a donation