Compression Streams API: Native gzip/deflate for the web

Crisis. Concept of reducing costs. Bag with dollars money in vise.
The Compression Streams API brings native, streaming gzip and deflate to the web platform. With built‑in CompressionStream and DecompressionStream, you can compress or decompress data on the fly—no third‑party libraries required. It integrates cleanly with Fetch/Response to turn streams into ArrayBuffer, Blob, Uint8Array, String, or JSON, works in Web Workers, and is part of the 2023 Baseline. Use it to shrink uploads, store compressed data offline, and streamline real‑time pipelines while keeping bundles lean.

Modern web apps move a lot of data. Until recently, if you wanted to compress or decompress that data in the browser, you had to ship a JavaScript library. The Compression Streams API changes that: it provides built‑in, streaming compression and decompression with gzip and deflate, so you can keep bundles lean and process data efficiently—even off the main thread.

What it is

Two interfaces:

    • CompressionStream — compresses a stream of data.
    • DecompressionStream — decompresses a stream of data.

Works with gzip or deflate.

Available in Web Workers.

Plays nicely with the Fetch API and Response utilities so you can turn streams into ArrayBuffer, Blob, Uint8Array, String, or JSON.

Part of the Compression specification (compression-stream).

This feature is part of the 2023 Baseline, meaning it’s broadly available in current browsers, though older ones may lack support.

Why it matters

Practical use cases

 

  • Client‑side uploads: compress large files before sending to your server.
  • Offline/Service Worker: store compressed assets or logs in IndexedDB.
  • Real‑time pipelines: compress analytics events or chat logs on the fly.
  • Import/export tools: shrink download sizes for user data backups.
 
				
					<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Compression Stream API Demo</title>
    <link data-wphbdelayedstyle="style.css" rel="stylesheet">
    <script type="wphb-delay-type" src="compressionStreamAPI.js" defer></script>
</head>

<body>
    <h1>Compression Streams API Demo</h1>
    <p id="support"></p>
    
    <h2>1) Compress/Decompress Text</h2>
    <label>
        Algorithm:
        <select id="algo">
            <option value="gzip" selected>gzip</option>
            <option value="deflate">deflate</option>
        </select>
    </label>
    <textarea id="textInput" rows="5" class="full-width" placeholder="Type or paste some text here..."></textarea>
    <button id="btnCompressText">Compress Text</button>
    <button id="btnDecompressText">Decompress Text</button>
    <div id="textResult" class="pre-box"></div>
    
    <h2>2) Compress/Decompress Files (Streaming)</h2>
    <div class="grid-gap">
        <div>
            <label>Pick a file to compress: <input type="file" id="fileToCompress"></label>
            <button id="btnCompressFile" disabled>Compress File</button>
            <div id="fileCompressResult"></div>
        </div>
        <div>
            <label>Pick a .gz or deflated file to decompress: <input type="file" id="fileToDecompress"></label>
            <button id="btnDecompressFile" disabled>Decompress File</button>
            <div id="fileDecompressResult"></div>
        </div>
    </div>

    <h2>3) Streaming Decompress from Fetch</h2>
    <p class="my-025">Enter a URL to a gzip-compressed text file (e.g., a URL to a .gz file)</p>
    <input id="fetchUrl" type="url" class="full-width" placeholder="Enter URL here...">
    <div class="mt-05">
        <button id="btnFetchDecompress">Fetch and Decompress</button>
        <pre id="fetchResult" class="pre-box"></pre>
    </div>
<script type="text/javascript" id="wphb-delayed-styles-js">
			(function () {
				const events = ["keydown", "mousemove", "wheel", "touchmove", "touchstart", "touchend"];
				function wphb_load_delayed_stylesheets() {
					document.querySelectorAll("link[data-wphbdelayedstyle]").forEach(function (element) {
						element.setAttribute("href", element.getAttribute("data-wphbdelayedstyle"));
					}),
						 events.forEach(function (event) {
						  window.removeEventListener(event, wphb_load_delayed_stylesheets, { passive: true });
						});
				}
			   events.forEach(function (event) {
				window.addEventListener(event, wphb_load_delayed_stylesheets, { passive: true });
			  });
			})();
		</script></body>

</html>
				
			
				
					/* Layout helpers */
.full-width {
    width: 100%;
}

.grid-gap {
    display: grid;
    gap: 0.5rem;
}

.mt-05 {
    margin-top: 0.5rem;
}

.my-025 {
    margin: 0.25rem 0;
}

/* Replaces the inline pre/box styling used for results and previews */
.pre-box {
    white-space: pre-wrap;
    background: #f6f8fa;
    padding: 0.75rem;
    border-radius: 6px;
}

/* Optional: small base styles (add/remove as you prefer) */
/*
body {
  font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
  line-height: 1.4;
  max-width: 800px;
  margin: 2rem auto;
  padding: 0 1rem;
}
*/
				
			
				
					// Feature detection: CompressionStream and DecompressionStream
function supportsCompressionStreams() {
	return (
		typeof CompressionStream !== 'undefined' &&
		typeof DecompressionStream !== 'undefined'
	);
}

const supportEl = document.getElementById('support');
if (supportsCompressionStreams()) {
	supportEl.textContent =
		'✅ Compression Streams API supported in this browser (works in workers too).';
} else {
	supportEl.textContent =
		'⚠️ Compression Streams API not supported. Consider a fallback (e.g., pako).';
}

// Helpers: text <-> bytes
const enc = new TextEncoder();
const dec = new TextDecoder();

// Convert a ReadableStream to a Uint8Array
async function streamToUint8Array(readable) {
	const ab = await new Response(readable).arrayBuffer();
	return new Uint8Array(ab);
}

// Convert a ReadableStream to a Blob
function streamToBlob(readable) {
	return new Response(readable).blob();
}

// Core: compress/decompress Uint8Array via streams
async function compressUint8(data, algorithm = 'gzip') {
	// Turn Uint8Array into a ReadableStream via Blob, pipe through CompressionStream
	const compressedReadable = new Blob([data])
		.stream()
		.pipeThrough(new CompressionStream(algorithm));
	return await streamToUint8Array(compressedReadable);
}

async function decompressUint8(data, algorithm = 'gzip') {
	const decompressedReadable = new Blob([data])
		.stream()
		.pipeThrough(new DecompressionStream(algorithm));
	return await streamToUint8Array(decompressedReadable);
}

// Convenience: text compress/decompress
async function compressText(text, algorithm = 'gzip') {
	const bytes = enc.encode(text);
	return await compressUint8(bytes, algorithm);
}

async function decompressToText(bytes, algorithm = 'gzip') {
	const out = await decompressUint8(bytes, algorithm);
	return dec.decode(out);
}

// Blob versions for files
async function compressBlobToBlob(blob, algorithm = 'gzip') {
	const compressedReadable = blob
		.stream()
		.pipeThrough(new CompressionStream(algorithm));
	return await streamToBlob(compressedReadable);
}

async function decompressBlobToBlob(blob, algorithm = 'gzip') {
	const decompressedReadable = blob
		.stream()
		.pipeThrough(new DecompressionStream(algorithm));
	return await streamToBlob(decompressedReadable);
}

// Utility: pretty size
function prettyBytes(n) {
	if (!Number.isFinite(n)) return '0 B';
	const units = ['B', 'KB', 'MB', 'GB'];
	let i = 0;
	while (n >= 1024 && i < units.length - 1) {
		n /= 1024;
		i++;
	}
	return `${n.toFixed(1)} ${units[i]}`;
}

/* --------------------------
   1) Text demo handlers
--------------------------- */
const algoSel = document.getElementById('algo');
const textInput = document.getElementById('textInput');
const textResult = document.getElementById('textResult');
const btnCompressText = document.getElementById('btnCompressText');
const btnDecompressText = document.getElementById('btnDecompressText');

let lastCompressedBytes = null;
let lastAlgorithm = 'gzip';

btnCompressText.addEventListener('click', async () => {
	const algorithm = algoSel.value;
	lastAlgorithm = algorithm;

	const text = textInput.value;
	if (!text) {
		textResult.textContent = 'Please enter some text.';
		return;
	}

	try {
		const originalBytes = enc.encode(text);
		const compressed = await compressUint8(originalBytes, algorithm);
		lastCompressedBytes = compressed;

		textResult.textContent =
			`Compressed (${algorithm})\n` +
			`Original size: ${prettyBytes(originalBytes.length)}\n` +
			`Compressed size: ${prettyBytes(compressed.length)}\n` +
			`Ratio: ${(compressed.length / originalBytes.length).toFixed(2)}\n\n` +
			`First 64 bytes (hex): ${Array.from(compressed.slice(0, 64))
				.map((b) => b.toString(16).padStart(2, '0'))
				.join(' ')}`;

		btnDecompressText.disabled = false;
	} catch (err) {
		console.error(err);
		textResult.textContent = `Error compressing text: ${err.message}`;
		btnDecompressText.disabled = true;
	}
});

btnDecompressText.addEventListener('click', async () => {
	if (!lastCompressedBytes) return;
	try {
		const decompressedText = await decompressToText(
			lastCompressedBytes,
			lastAlgorithm
		);
		textResult.textContent = `Decompressed back to:\n\n${decompressedText}`;
	} catch (err) {
		console.error(err);
		textResult.textContent = `Error decompressing text: ${err.message}`;
	}
});

/* -----------------------------------
   2) File compress/decompress demo
------------------------------------ */
const fileToCompress = document.getElementById('fileToCompress');
const fileToDecompress = document.getElementById('fileToDecompress');
const btnCompressFile = document.getElementById('btnCompressFile');
const btnDecompressFile = document.getElementById('btnDecompressFile');
const fileCompressResult = document.getElementById('fileCompressResult');
const fileDecompressResult = document.getElementById('fileDecompressResult');

fileToCompress.addEventListener('change', () => {
	btnCompressFile.disabled = !fileToCompress.files?.length;
});
fileToDecompress.addEventListener('change', () => {
	btnDecompressFile.disabled = !fileToDecompress.files?.length;
});

btnCompressFile.addEventListener('click', async () => {
	const file = fileToCompress.files?.[0];
	if (!file) return;

	const algorithm = algoSel.value; // 'gzip' or 'deflate'
	fileCompressResult.textContent = 'Compressing...';

	try {
		const compressedBlob = await compressBlobToBlob(file, algorithm);

		// Provide a download link; extension is illustrative
		const ext = algorithm === 'gzip' ? '.gz' : '.deflate';
		const url = URL.createObjectURL(compressedBlob);
		const a = document.createElement('a');
		a.href = url;
		a.download = `${file.name}${ext}`;
		a.textContent = `Download ${a.download}`;

		fileCompressResult.innerHTML =
			`Original: ${file.name} (${prettyBytes(
				file.size
			)}) → Compressed: ${prettyBytes(compressedBlob.size)} ` +
			`(ratio ${(compressedBlob.size / file.size).toFixed(2)}) `;
		fileCompressResult.appendChild(a);
	} catch (err) {
		console.error(err);
		fileCompressResult.textContent = `Error: ${err.message}`;
	}
});

btnDecompressFile.addEventListener('click', async () => {
	const file = fileToDecompress.files?.[0];
	if (!file) return;

	// You need to know which algorithm was used. We'll attempt gzip first, then deflate.
	fileDecompressResult.textContent = 'Decompressing...';

	try {
		let decompressedBlob;
		try {
			decompressedBlob = await decompressBlobToBlob(file, 'gzip');
		} catch {
			decompressedBlob = await decompressBlobToBlob(file, 'deflate');
		}

		const url = URL.createObjectURL(decompressedBlob);
		const a = document.createElement('a');
		a.href = url;
		a.download =
			file.name.replace(/\.(gz|deflate)$/i, '') || 'decompressed.bin';
		a.textContent = `Download ${a.download}`;

		// If it looks like text, show a preview
		const head = await decompressedBlob
			.slice(0, 512)
			.text()
			.catch(() => '');
		const looksText = /^[\x09\x0A\x0D\x20-\x7E]/.test(head);
		const preview = document.createElement('pre');
		preview.style.whiteSpace = 'pre-wrap';
		preview.style.background = '#f6f8fa';
		preview.style.padding = '.5rem';
		preview.style.borderRadius = '6px';
		preview.textContent = looksText
			? await decompressedBlob.text()
			: '(binary data)';

		fileDecompressResult.innerHTML = `Decompressed size: ${prettyBytes(
			decompressedBlob.size
		)} `;
		fileDecompressResult.appendChild(a);
		fileDecompressResult.appendChild(
			document.createElement('div')
		).textContent = 'Preview:';
		fileDecompressResult.appendChild(preview);
	} catch (err) {
		console.error(err);
		fileDecompressResult.textContent = `Error: ${err.message}`;
	}
});

/* ----------------------------------------
   3) Streaming decompression via fetch()
----------------------------------------- */
const fetchUrl = document.getElementById('fetchUrl');
const fetchResult = document.getElementById('fetchResult');
const btnFetchDecompress = document.getElementById('btnFetchDecompress');

btnFetchDecompress.addEventListener('click', async () => {
	const url = fetchUrl.value.trim();
	if (!url) {
		fetchResult.textContent =
			'Please enter a URL to a gzip-compressed resource (e.g., *.txt.gz).';
		return;
	}
	fetchResult.textContent = 'Fetching...';

	try {
		const res = await fetch(url);
		if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);

		// If the server didn’t set Content-Encoding but the payload is gzip-compressed (e.g., .gz file),
		// you can manually decompress the response body stream:
		const decompressedStream = res.body.pipeThrough(
			new DecompressionStream('gzip')
		);
		const text = await new Response(decompressedStream).text();

		fetchResult.textContent =
			text.slice(0, 5000) + (text.length > 5000 ? '\n…(truncated)…' : '');
	} catch (err) {
		console.error(err);
		fetchResult.textContent = `Fetch/decompress error: ${err.message}`;
	}
});

/* ---------------------------------------------------
   Optional: Offload compression to a Web Worker
   (keeps UI responsive for big payloads)
---------------------------------------------------- */
function createCompressionWorker() {
	const workerCode = `
    const enc = new TextEncoder();
    const dec = new TextDecoder();

    async function compressUint8(data, algorithm) {
      const compressedReadable = new Blob([data]).stream()
        .pipeThrough(new CompressionStream(algorithm));
      const ab = await new Response(compressedReadable).arrayBuffer();
      return new Uint8Array(ab);
    }

    async function decompressUint8(data, algorithm) {
      const decompressedReadable = new Blob([data]).stream()
        .pipeThrough(new DecompressionStream(algorithm));
      const ab = await new Response(decompressedReadable).arrayBuffer();
      return new Uint8Array(ab);
    }

    self.onmessage = async (e) => {
      const { op, algorithm, payload } = e.data;
      try {
        let out;
        if (op === 'compressText') {
          out = await compressUint8(enc.encode(payload), algorithm);
          self.postMessage({ ok: true, out }, [out.buffer]); // transfer the buffer
        } else if (op === 'decompressToText') {
          const bytes = new Uint8Array(payload);
          const outBytes = await decompressUint8(bytes, algorithm);
          self.postMessage({ ok: true, out: dec.decode(outBytes) });
        }
      } catch (err) {
        self.postMessage({ ok: false, error: err.message });
      }
    };
  `;
	const blob = new Blob([workerCode], { type: 'application/javascript' });
	return new Worker(URL.createObjectURL(blob), { type: 'module' });
}

// Example usage:
// const w = createCompressionWorker();
// w.postMessage({ op: 'compressText', algorithm: 'gzip', payload: 'Hello worker!' });
// w.onmessage = (e) => console.log('Worker result:', e.data);

				
			

The Compression Streams API brings first‑class, streaming gzip/deflate to the platform. With native primitives, you get smaller bundles, better performance, and simpler code for data‑heavy web apps. For details and the latest updates, see “Compression Streams API” on MDN.

More To Explore

Crisis. Concept of reducing costs. Bag with dollars money in vise.
Code

Compression Streams API: Native gzip/deflate for the web

The Compression Streams API brings native, streaming gzip and deflate to the web platform. With built‑in CompressionStream and DecompressionStream, you can compress or decompress data on the fly—no third‑party libraries required. It integrates cleanly with Fetch/Response to turn streams into ArrayBuffer, Blob, Uint8Array, String, or JSON, works in Web Workers, and is part of the 2023 Baseline. Use it to shrink uploads, store compressed data offline, and streamline real‑time pipelines while keeping bundles lean.

copy and paste concept
Code

Quick Tour of the Clipboard API

The Clipboard API brings modern, asynchronous copy, cut, and paste to web apps. Access Navigator.clipboard in secure contexts, handle copy/cut/paste events, and manage permissions and user activation. Note that Chromium, Firefox, and Safari differ on permissions and transient activation, so test across browsers.

Share This Post

small_c_popup.png

Need help?

Let's have a chat...


Login

Jump Back In!

Here at Webolution Designs, we love to learn. This includes sharing things we have learned with you. 

Register

Begin Your Learning Journey Today!

Come back inside to continue your learning journey.