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

modern-tar

v0.8.5

Published

Zero dependency streaming tar parser and writer for JavaScript.

Readme

🗄️ modern-tar

Zero-dependency, cross-platform, streaming tar archive library for every JavaScript runtime. Built with the browser-native Web Streams API for performance and memory efficiency.

Features

  • 🚀 Streaming Architecture - Supports large archives without loading everything into memory.
  • 📋 Standards Compliant - Full USTAR format support with PAX extensions. Compatible with GNU tar, BSD tar, and other standard implementations.
  • 🗜️ Compression - Supports gzip compression and decompression with native compression streams.
  • 📝 TypeScript First - Full type safety with detailed TypeDoc documentation.
  • Zero Dependencies - No external dependencies, minimal bundle size.
  • 🌐 Cross-Platform - Works in browsers, Node.js, Cloudflare Workers, and other JavaScript runtimes.
  • 📁 Node.js Integration - Additional high-level APIs for directory packing and extraction.

Installation

npm install modern-tar

Usage

This package provides two entry points:

  • modern-tar: The core, cross-platform streaming API (works everywhere).
  • modern-tar/fs: High-level filesystem utilities for Node.js.

Core Usage

These APIs use the Web Streams API and can be used in any modern JavaScript environment.

Simple

import { packTar, unpackTar } from 'modern-tar';

// Entry bodies can be strings, bytes, Blobs, or ReadableStreams.
const archive = await packTar([
	{ header: { name: "file.txt", size: 5 }, body: "hello" },
	{ header: { name: "dir/", type: "directory", size: 0 } },
	{ header: { name: "dir/nested.txt", size: 3 }, body: new Uint8Array([97, 98, 99]) } // "abc"
]);

// Unpack tar buffer into entries
const extracted = await unpackTar(archive);
const decoder = new TextDecoder();

for (const { header, data } of extracted) {
	if (data) {
		console.log(`${header.name}: ${decoder.decode(data)}`);
	} else {
		console.log(`${header.type}: ${header.name}`);
	}
}

Streaming

import { createTarDecoder } from 'modern-tar';

const response = await fetch('/archive.tar');
if (!response.ok) throw new Error(`Download failed: ${response.status}`);
if (!response.body) throw new Error('No response body');

const entries = response.body.pipeThrough(createTarDecoder());
for await (const entry of entries) {
	if (!entry.header.name.endsWith(".txt")) {
		// You MUST read each entry body completely before advancing.
		// If an entry is not needed, cancel its body so the stream does not stall.
		await entry.body.cancel();
		continue;
	}

	const text = await new Response(entry.body).text();
	console.log(`${entry.header.name}: ${text}`);
}

Compression/Decompression (gzip)

import { createTarPacker } from 'modern-tar';

const { readable, controller } = createTarPacker();
const gzipResponse = new Response(
  readable.pipeThrough(new CompressionStream('gzip')),
  { headers: { 'Content-Type': 'application/gzip' } }
);

const body = controller.add({ name: 'hello.txt', size: 5 });
const writer = body.getWriter();
await writer.write(new TextEncoder().encode('hello'));
await writer.close();
controller.finalize();

// Return gzipResponse from your server or Worker handler.
import { unpackTar } from 'modern-tar';

// Download and unpack a .tar.gz without buffering the compressed archive
const response = await fetch('https://api.example.com/archive.tar.gz');
if (!response.ok) throw new Error(`Download failed: ${response.status}`);
if (!response.body) throw new Error('No response body');

const entries = await unpackTar(
  response.body.pipeThrough(new DecompressionStream('gzip'))
);
const decoder = new TextDecoder();

for (const { header, data } of entries) {
  if (data) {
    console.log(`${header.name}: ${decoder.decode(data)}`);
  }
}

Node.js Filesystem Usage

These APIs use Node.js streams when interacting with the local filesystem.

Simple

import { packTar, unpackTar } from 'modern-tar/fs';
import { createWriteStream, createReadStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';

// Pack a directory into a tar file
const tarStream = packTar('./my/project');
const fileStream = createWriteStream('./project.tar');
await pipeline(tarStream, fileStream);

// Extract a tar file to a directory
const tarReadStream = createReadStream('./project.tar', {
	highWaterMark: 256 * 1024 // 256 KB for optimal performance
});
const extractStream = unpackTar('./output/directory');
await pipeline(tarReadStream, extractStream);

Filtering and Transformation

import { packTar, unpackTar } from 'modern-tar/fs';
import { createReadStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';

// Pack with filtering
const packStream = packTar('./my/project', {
	filter: (filePath, stats) => !filePath.includes('node_modules'),
	map: (header) => ({ ...header, mode: 0o644 }), // Set all files to 644
	dereference: true // Follow symlinks instead of archiving them
});

// Unpack with advanced options
const sourceStream = createReadStream('./archive.tar', {
	highWaterMark: 256 * 1024 // 256 KB for optimal performance
});
const extractStream = unpackTar('./output', {
	// Core options
	strip: 1, // Remove first directory level
	filter: (header) => header.name.endsWith('.js'), // Only extract JS files
	map: (header) => ({ ...header, name: header.name.toLowerCase() }), // Transform names

	// Filesystem-specific options
	fmode: 0o644, // Override file permissions
	dmode: 0o755, // Override directory permissions
	maxDepth: 50,  // Limit extraction depth for security (default: 1024)
	concurrency: 8 // Limit concurrent filesystem operations (default: CPU cores)
});

await pipeline(sourceStream, extractStream);

Archive Creation

import { packTar, type TarSource } from 'modern-tar/fs';
import { createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';

// Pack multiple sources
const sources: TarSource[] = [
  { type: 'file', source: './package.json', target: 'project/package.json' },
  { type: 'directory', source: './src', target: 'project/src' },
  { type: 'content', content: 'Hello World!', target: 'project/hello.txt' },
  { type: 'content', content: '#!/bin/bash\necho "Executable"', target: 'bin/script.sh', mode: 0o755 },
  { type: 'stream', content: createReadStream('./large-file.bin'), target: 'project/data.bin', size: 1048576 },
  { type: 'stream', content: fetch('/api/data').then(r => r.body!), target: 'project/remote.json', size: 2048 }
];

const archiveStream = packTar(sources);
await pipeline(archiveStream, createWriteStream('project.tar'));

Compression/Decompression (gzip)

import { packTar, unpackTar } from 'modern-tar/fs';
import { createWriteStream, createReadStream } from 'node:fs';
import { createGzip, createGunzip } from 'node:zlib';
import { pipeline } from 'node:stream/promises';

// Pack directory and compress to .tar.gz
const tarStream = packTar('./my/project');
await pipeline(tarStream, createGzip(), createWriteStream('./project.tar.gz'));

// Decompress and extract .tar.gz
const gzipStream = createReadStream('./project.tar.gz', {
	highWaterMark: 256 * 1024 // 256 KB for optimal performance
});
await pipeline(gzipStream, createGunzip(), unpackTar('./output'));

API Reference

See the API Reference.

Benchmarks

Current benchmarks indicate we're much faster than other popular tar libraries for small file archives (packing and unpacking). On the other hand, larger files hit an I/O bottleneck resulting in similar performance between libraries.

See the Results.

Compatibility

The core library uses the Web Streams API and requires:

  • Node.js: 18.0+
  • TypeScript: 5.7+ when consuming the bundled declarations
  • Browsers: Baseline Widely available
    • Chrome 85+
    • Edge 85+
    • Firefox 102+
    • Safari 14.1+

If you use CompressionStream or DecompressionStream, Firefox 113+ and Safari 16.4+ are required.

Acknowledgements

License

MIT