Modern Tar Archiver
modern-tar is a zero-dependency, cross-platform JavaScript library designed for efficient streaming of tar archives. It supports both parsing and writing tar files, leveraging the browser-native Web Streams API for optimal performance and memory efficiency across diverse JavaScript runtimes, including Node.js (requiring version 18.0.0 or higher), web browsers, and Cloudflare Workers. The library is currently at stable version 0.7.6, with a consistent release cadence that introduces bug fixes, performance optimizations, and crucial security patches. Its key differentiators include a robust streaming architecture capable of handling large archives without full memory loading, full compliance with USTAR format and PAX extensions, built-in helpers for gzip compression, a TypeScript-first design ensuring strong type safety, and a minimal footprint due to its zero external dependencies.
Common errors
-
TypeError: (0 , modern_tar__WEBPACK_IMPORTED_MODULE_0__.packTar) is not a function
cause Attempting to use CommonJS `require` syntax in an ES Module environment (e.g., modern Node.js or bundlers) for a library that exports named ES Modules.fixChange your import statement from `const { packTar } = require('modern-tar')` to `import { packTar } from 'modern-tar'`. -
Stream stalled or hangs indefinitely after processing a few entries.
cause The `body` ReadableStream of a tar entry was not fully read or explicitly cancelled before attempting to access the next entry from the decoder.fixEnsure that after processing each entry, you either fully consume its `entry.body` stream or explicitly call `await entry.body.cancel()` to signal that you are done with it and allow the decoder to proceed.
Warnings
- breaking The `streamTimeout` option has been removed from `UnpackOptions` due to a new performance architecture.
- breaking The `data` property returned by the `packTar` function for bodyless entries (like directories or symlinks) can now be `undefined` instead of an empty `Uint8Array`. For empty files, it will be `Uint8Array(0)`.
- breaking A prototype pollution vulnerability in PAX headers was fixed. Archives crafted to exploit this could modify object prototypes, potentially leading to security issues.
- breaking Security fixes were implemented to prevent a 32-bit integer overflow on meta header sizes and address issues with unicode path handling, which could lead to data corruption or unexpected behavior.
- gotcha When consuming a decoded tar stream (e.g., from `createTarDecoder`), you *must* explicitly drain or cancel the `body` of each `entry` before iterating to the next entry. Failure to do so will cause the stream to stall indefinitely.
Install
-
npm install modern-tar -
yarn add modern-tar -
pnpm add modern-tar
Imports
- packTar
const { packTar } = require('modern-tar')import { packTar } from 'modern-tar' - unpackTar
import unpackTar from 'modern-tar'
import { unpackTar } from 'modern-tar' - createTarPacker
import { createTarPacker } from 'modern-tar' - createGzipEncoder
import { createGzipEncoder } from 'modern-tar'
Quickstart
import { createTarPacker, createTarDecoder } from 'modern-tar';
async function processTarStream() {
// Create a tar packer
const { readable, controller } = createTarPacker();
// Add entries dynamically
const fileStream = controller.add({
name: "dynamic.txt",
size: 5,
type: "file"
});
// Write content to the stream
const writer = fileStream.getWriter();
await writer.write(new TextEncoder().encode("hello"));
await writer.close();
// Add another entry, maybe a directory
controller.add({ name: "my-dir/", type: "directory", size: 0 });
// When done adding entries, finalize the archive
controller.finalize();
// Pipe the archive right into a decoder
const decodedStream = readable.pipeThrough(createTarDecoder());
for await (const entry of decodedStream) {
console.log(`Decoded: ${entry.header.name}`);
const shouldSkip = entry.header.name.endsWith(".md");
if (shouldSkip) {
// You MUST drain the body with cancel() to proceed to the next entry or read it fully,
// otherwise the stream will stall.
await entry.body.cancel();
continue;
}
// Example: Read the content for non-skipped files
if (entry.header.type === 'file') {
const reader = entry.body.getReader();
let chunk = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunk += new TextDecoder().decode(value);
}
console.log(`Content of ${entry.header.name}: ${chunk}`);
}
}
console.log('Tar stream processing complete.');
}
processTarStream().catch(console.error);