Native Node.js bindings for liblzma — XZ/LZMA2 compression with browser support via WebAssembly.
XZ is a container for compressed archives. It offers one of the best compression ratios available, with a good balance between compression time and decompression speed/memory.
Only LZMA2 is supported for compression output. But the library can open and read any LZMA1 or LZMA2 compressed file.
npm install node-liblzma
import { xzAsync, unxzAsync, createXz, createUnxz } from 'node-liblzma';
// Simple: Compress a buffer
const compressed = await xzAsync(Buffer.from('Hello, World!'));
const decompressed = await unxzAsync(compressed);
// Streaming: Compress a file
import { createReadStream, createWriteStream } from 'fs';
createReadStream('input.txt')
.pipe(createXz())
.pipe(createWriteStream('output.xz'));
// With progress monitoring
const compressor = createXz();
compressor.on('progress', ({ bytesRead, bytesWritten }) => {
console.log(`${bytesRead} bytes in → ${bytesWritten} bytes out`);
});
import { xzAsync, unxzAsync } from 'node-liblzma';
xzAsync(Buffer.from('Hello, World!'))
.then(compressed => {
console.log('Compressed size:', compressed.length);
return unxzAsync(compressed);
})
.then(decompressed => {
console.log('Decompressed:', decompressed.toString());
})
.catch(err => {
console.error('Compression failed:', err);
});
import { xz, unxz } from 'node-liblzma';
xz(Buffer.from('Hello, World!'), (err, compressed) => {
if (err) throw err;
unxz(compressed, (err, decompressed) => {
if (err) throw err;
console.log('Decompressed:', decompressed.toString());
});
});
📖 Full API documentation: oorabona.github.io/node-liblzma
Live Demo — Try XZ compression in your browser.
xzAsync, unxzAsync, createXz, createUnxz)import from 'node-liblzma/inline'-z, -d, -l, -k, -f, -c, -o, -v, -q-e)tar)-B).tar.xz archives — Node.js streaming + browser WASMxzAsync() and unxzAsync()In previous versions, N-API replaced nan as the stable ABI for native modules.
Tested on: Linux x64, macOS (x64/arm64), Raspberry Pi 2/3/4, Windows.
Prebuilt binaries are bundled for: Windows x64, Linux x64, macOS x64/arm64.
| Flag | Description | Default | Values |
|---|---|---|---|
USE_GLOBAL |
Use system liblzma library | yes (no on Windows) |
yes, no |
RUNTIME_LINK |
Static or shared linking | shared |
static, shared |
ENABLE_THREAD_SUPPORT |
Enable thread support | yes |
yes, no |
If no prebuilt binary matches your platform, node-gyp will compile from source automatically.
Live Demo — Try XZ compression in your browser right now.
node-liblzma v3.0.0+ supports XZ compression in the browser via WebAssembly. The same API works in both Node.js and browsers — bundlers (Vite, Webpack, esbuild) automatically resolve the WASM-backed implementation.
// Bundlers auto-resolve to WASM in browser, native in Node.js
import { xzAsync, unxzAsync, isXZ } from 'node-liblzma';
// Compress
const compressed = await xzAsync('Hello, browser!');
// Decompress
const original = await unxzAsync(compressed);
// Check if data is XZ-compressed
if (isXZ(someBuffer)) {
const data = await unxzAsync(someBuffer);
}
import { createXz, createUnxz } from 'node-liblzma';
// Compress a fetch response
const response = await fetch('/large-file.bin');
const compressed = response.body.pipeThrough(createXz({ preset: 6 }));
// Decompress
const decompressed = compressedStream.pipeThrough(createUnxz());
| Import | When to use |
|---|---|
node-liblzma |
Standard — bundler resolves to WASM (browser) or native (Node.js) |
node-liblzma/wasm |
Explicit WASM usage in Node.js (no native addon needed) |
node-liblzma/inline |
Zero-config — WASM embedded as base64 (no external file to serve) |
// Explicit WASM (works in Node.js too, no native build required)
import { xzAsync } from 'node-liblzma/wasm';
// Inline mode (larger bundle, but no WASM file to configure)
import { ensureInlineInit, xzAsync } from 'node-liblzma/inline';
await ensureInlineInit(); // Decodes embedded base64 WASM
const compressed = await xzAsync(data);
xzSync() / unxzSync() throw LZMAError in browsersxzFile() / unxzFile() are not availablecreateXz() / createUnxz() (Web TransformStream) instead of Xz / Unxz classes| Component | Raw | Gzipped |
|---|---|---|
| liblzma.wasm | ~107KB | ~52KB |
| Glue code (liblzma.js) | ~6KB | ~2KB |
| Total | ~113KB | ~54KB |
For detailed browser setup instructions, see docs/BROWSER.md.
This package includes nxz, a portable xz-like CLI tool that works on any platform with Node.js.
# Global installation (recommended for CLI usage)
npm install -g node-liblzma
# Then use directly
nxz --help
# Compress a file (creates file.txt.xz, deletes original)
nxz file.txt
# Decompress (auto-detected from .xz extension)
nxz file.txt.xz
# Keep original file (-k)
nxz -k file.txt
# Maximum compression (-9) with extreme mode (-e)
nxz -9e large-file.bin
# Compress to stdout (-c) for piping
nxz -c file.txt > file.txt.xz
# List archive info (-l / -lv for verbose)
nxz -l file.txt.xz
# Benchmark native vs WASM performance (-B)
nxz -B file.txt
nxz can create, list, and extract .tar.xz archives — auto-detected from file extension:
# Create a tar.xz archive from files/directories
nxz -T src/ lib/ README.md -o project.tar.xz
# List archive contents
nxz -Tl project.tar.xz
# Extract archive to a directory
nxz -Td project.tar.xz -o output/
# Extract with path stripping (like tar --strip-components)
nxz -Td project.tar.xz --strip=1 -o output/
Archives created by nxz are fully compatible with system tar -xJf.
| Option | Long | Description |
|---|---|---|
-z |
--compress |
Force compression mode |
-d |
--decompress |
Force decompression mode |
-l |
--list |
List archive information |
-T |
--tar |
Treat file as tar.xz archive (auto-detected for .tar.xz/.txz) |
-B |
--benchmark |
Benchmark native vs WASM performance |
-k |
--keep |
Keep original file (don't delete) |
-f |
--force |
Overwrite existing output file |
-c |
--stdout |
Write to stdout, keep original file |
-o |
--output=FILE |
Write output to specified file (or directory for tar extract) |
-v |
--verbose |
Show progress for large files |
-q |
--quiet |
Suppress warning messages |
-0..-9 |
Compression level (default: 6) | |
-e |
--extreme |
Extreme compression (slower) |
--strip=N |
Strip N leading path components on tar extract | |
-h |
--help |
Show help |
-V |
--version |
Show version |
# Standalone nxz-cli package (recommended — smaller, faster install)
npx nxz-cli --help
pnpm dlx nxz-cli --help
# Or via the full node-liblzma package
npx --package node-liblzma nxz --help
pnpm dlx --package node-liblzma nxz --help
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Error (file not found, format error, etc.) |
| 130 | Interrupted (SIGINT/Ctrl+C) |
node-liblzma powers a family of focused packages:
| Package | Description | Install |
|---|---|---|
node-liblzma |
Core XZ library — Node.js native + browser WASM | npm i node-liblzma |
tar-xz |
Create/extract .tar.xz archives — Node.js + browser | npm i tar-xz |
nxz-cli |
Standalone CLI — npx nxz-cli file.txt |
npx nxz-cli |
Live Demo — Create and extract tar.xz archives in your browser.
A library for working with .tar.xz archives, with dual Node.js (streaming) and browser (buffer-based) APIs. This fills the gap left by node-tar which does not support .tar.xz.
// Node.js — streaming API
import { create, extract, list } from 'tar-xz';
await create({ file: 'archive.tar.xz', cwd: './src', files: ['index.ts', 'utils.ts'] });
const entries = await list({ file: 'archive.tar.xz' });
await extract({ file: 'archive.tar.xz', cwd: './output' });
// Browser — buffer-based API
import { createTarXz, extractTarXz, listTarXz } from 'tar-xz';
const archive = await createTarXz({ files: [{ name: 'hello.txt', content: data }] });
const files = await extractTarXz(archive);
const entries = await listTarXz(archive);
A lightweight wrapper package for running nxz without installing the full node-liblzma:
# No install needed
npx nxz-cli file.txt # compress
npx nxz-cli -d file.txt.xz # decompress
npx nxz-cli -T src/ -o app.tar.xz # create tar.xz archive
# Or install globally
npm install -g nxz-cli
The API mirrors Node.js Zlib for easy adoption:
// CommonJS
var lzma = require('node-liblzma');
// TypeScript / ES6 modules
import * as lzma from 'node-liblzma';
| Zlib | node-liblzma | Arguments |
|---|---|---|
createGzip |
createXz |
([options]) |
createGunzip |
createUnxz |
([options]) |
gzip |
xz |
(buf, [options], callback) |
gunzip |
unxz |
(buf, [options], callback) |
gzipSync |
xzSync |
(buf, [options]) |
gunzipSync |
unxzSync |
(buf, [options]) |
| - | xzAsync |
(buf, [options]) → Promise<Buffer> |
| - | unxzAsync |
(buf, [options]) → Promise<Buffer> |
| - | xzFile |
(input, output, [options]) → Promise<void> |
| - | unxzFile |
(input, output, [options]) → Promise<void> |
| Attribute | Type | Description | Values |
|---|---|---|---|
check |
number | Integrity check | check.NONE, check.CRC32, check.CRC64, check.SHA256 |
preset |
number | Compression level (0-9) | preset.DEFAULT (6), preset.EXTREME |
mode |
number | Compression mode | mode.FAST, mode.NORMAL |
threads |
number | Thread count | 0 = auto (all cores), 1 = single-threaded, N = N threads |
filters |
array | Filter chain | filter.LZMA2, filter.X86, filter.ARM, etc. |
chunkSize |
number | Processing chunk size | Default: 64KB |
For further information, see the XZ SDK documentation.
Multi-threaded compression is available when built with ENABLE_THREAD_SUPPORT=yes (default).
| Value | Behavior |
|---|---|
0 |
Auto-detect: use all available CPU cores |
1 |
Single-threaded (default) |
N |
Use exactly N threads |
import { createXz, hasThreads } from 'node-liblzma';
if (hasThreads()) {
const compressor = createXz({ threads: 0 }); // auto-detect
}
Note: Threads only apply to compression, not decompression.
Track compression and decompression progress in real-time:
import { createXz, createUnxz } from 'node-liblzma';
const compressor = createXz({ preset: 6 });
compressor.on('progress', ({ bytesRead, bytesWritten }) => {
const ratio = bytesWritten / bytesRead;
console.log(`Progress: ${bytesRead} in, ${bytesWritten} out (ratio: ${ratio.toFixed(2)})`);
});
inputStream.pipe(compressor).pipe(outputStream);
Progress events fire after each chunk is processed. Works with streams, not buffer APIs.
For production environments with high concurrency needs:
import { LZMAPool } from 'node-liblzma';
const pool = new LZMAPool(10); // Max 10 concurrent operations
pool.on('metrics', (metrics) => {
console.log(`Active: ${metrics.active}, Queued: ${metrics.queued}`);
});
const compressed = await pool.compress(buffer);
const decompressed = await pool.decompress(compressed);
import { xzFile, unxzFile } from 'node-liblzma';
await xzFile('input.txt', 'output.txt.xz');
await unxzFile('output.txt.xz', 'restored.txt');
// With options
await xzFile('large-file.bin', 'compressed.xz', { preset: 9, threads: 4 });
Handles files > 512MB automatically via streams with lower memory footprint.
Typed error classes for precise error handling:
import {
xzAsync, LZMAError, LZMAMemoryError, LZMADataError, LZMAFormatError
} from 'node-liblzma';
try {
const compressed = await xzAsync(buffer);
} catch (error) {
if (error instanceof LZMAMemoryError) {
console.error('Out of memory:', error.message);
} else if (error instanceof LZMADataError) {
console.error('Corrupt data:', error.message);
} else if (error instanceof LZMAFormatError) {
console.error('Invalid format:', error.message);
}
}
Available error classes: LZMAError (base), LZMAMemoryError, LZMAMemoryLimitError, LZMAFormatError, LZMAOptionsError, LZMADataError, LZMABufferError, LZMAProgrammingError.
const stream = createXz({
preset: lzma.preset.DEFAULT,
chunkSize: 256 * 1024 // 256KB chunks (default: 64KB)
});
| File Size | Recommended chunkSize |
|---|---|
| < 1MB | 64KB (default) |
| 1-10MB | 128-256KB |
| > 10MB | 512KB-1MB |
Maximum buffer size: 512MB per operation (security limit). For larger files, use streaming APIs.
The low-level native callback follows an errno-style contract matching liblzma behavior:
(errno: number, availInAfter: number, availOutAfter: number)errno is LZMA_OK or LZMA_STREAM_ENDerrno valueHigh-level APIs remain ergonomic — Promise functions resolve to Buffer or reject with Error, stream users listen to error events.
All three backends use the same liblzma library and produce identical compression ratios:
System xz > nxz native (C++ addon) > nxz WASM (Emscripten)
fastest ~1-2x slower ~2-5x slower (decompress)
~1x (compress, large files)
| Backend | Compress | Decompress | Size | Environment |
|---|---|---|---|---|
System xz 5.8 |
81 ms | 4 ms | 76.7 KB | C binary |
| nxz native | 90 ms | 3.4 ms | 76.7 KB | Node.js + C++ addon |
| nxz WASM | 86 ms | 7.9 ms | 76.7 KB | Node.js + Emscripten |
| Data | Compress | Decompress | Notes |
|---|---|---|---|
| 1 KB text | WASM 2.8x slower | WASM 4.9x slower | Startup overhead dominates |
| 135 KB binary | ~1:1 | WASM 2x slower | Compression near-parity |
| 246 KB source | ~1:1 | WASM 2.3x slower | Realistic workload |
| 1 MB random | ~1:1 | WASM 1.6x slower | Gap narrows with size |
# Compare nxz (native) vs system xz across file sizes
./scripts/benchmark.sh
./scripts/benchmark.sh -s 1,50,200 -p 6,9 # custom sizes/presets
./scripts/benchmark.sh -o csv > results.csv # export as CSV/JSON
# Compare native addon vs WASM backend
nxz --benchmark file.txt
nxz -B -3 large-file.bin # with preset 3
| Scenario | Recommended |
|---|---|
| Browser | WASM (only option) |
| Node.js, performance-critical | Native addon |
| Node.js, no C++ toolchain available | WASM (node-liblzma/wasm) |
| Cross-platform scripts | nxz CLI |
| Batch processing many files | System xz |
| CI/CD with Node.js already installed | nxz CLI |
npm install node-liblzma
# or
pnpm add node-liblzma
If prebuilt binaries don't match your platform, install system development libraries:
# Debian/Ubuntu
sudo apt-get install liblzma-dev
# macOS
brew install xz
# Windows (automatic download and build)
npm install node-liblzma --build-from-source
# Force rebuild with default options
npm install node-liblzma --build-from-source
# Disable thread support
ENABLE_THREAD_SUPPORT=no npm install node-liblzma --build-from-source
If you compiled XZ outside of node-liblzma:
export CPATH=$HOME/path/to/headers
export LIBRARY_PATH=$HOME/path/to/lib
export LD_LIBRARY_PATH=$HOME/path/to/lib:$LD_LIBRARY_PATH
npm install
pnpm test # Run all 519 tests
pnpm test:watch # Watch mode
pnpm test:coverage # Coverage report
pnpm type-check # TypeScript type checking
Tests use Vitest with 100% code coverage across statements, branches, functions, and lines.
import instead of require)// Promise-based APIs (recommended)
const compressed = await xzAsync(buffer);
// Typed error classes
import { LZMAMemoryError, LZMADataError } from 'node-liblzma';
// Concurrency control
const pool = new LZMAPool(10);
const results = await Promise.all(files.map(f => pool.compress(f)));
// File helpers
await xzFile('input.txt', 'output.txt.xz');
We welcome contributions! See the full contributing guidelines below.
git clone https://github.com/oorabona/node-liblzma.git
cd node-liblzma
pnpm install
pnpm build
pnpm test
pnpm test # Run tests
pnpm test:watch # Watch mode
pnpm test:coverage # Coverage report
pnpm check # Lint + format check (Biome)
pnpm check:write # Auto-fix lint/format
pnpm type-check # TypeScript types
We follow Conventional Commits:
feat: add LZMAPool for concurrency control
fix: resolve memory leak in FunctionReference
docs: add migration guide for v2.0
feat/, fix/, refactor/, docs/)pnpm check:write && pnpm type-check && pnpm test"Cannot find liblzma library" — Install system dev package:
sudo apt-get install liblzma-dev # Debian/Ubuntu
brew install xz # macOS
"node-gyp rebuild failed" — Install build tools:
sudo apt-get install build-essential python3 # Linux
xcode-select --install # macOS
npm install --global windows-build-tools # Windows
"Prebuilt binary not found" — Build from source:
npm install node-liblzma --build-from-source
LZMAMemoryError — Input too large for buffer API. Use streaming:
createReadStream('large.bin').pipe(createXz()).pipe(createWriteStream('large.xz'));
LZMADataError — File is not XZ-compressed or is corrupted. Verify with file compressed.xz or xz -t compressed.xz.
Slow on multi-core — Enable threads: createXz({ threads: 0 }) (auto-detect cores).
High memory with concurrency — Use LZMAPool to limit simultaneous operations.
Build fails — Install Visual Studio Build Tools and set Python:
npm install --global windows-build-tools
npm config set python python3
Path issues — Use path.join() instead of hardcoded separators.
If you find one, feel free to contribute and post a new issue! PRs are accepted as well :)
If you compile with threads, you may see warnings about -Wmissing-field-initializers.
This is normal and does not prevent threading from being active and working.
Kudos to addaleax for helping out with C++ stuff!
This software is released under LGPL-3.0+.