zip-peek
v0.2.2
Published
Service worker based ZIP asset loader for browser applications.
Keywords
Readme
zip-peek
zip-peek lets browser applications load files from a remote .zip archive by only requesting the needed range of bytes without downloading and extracting the full ZIP file.
It registers a service worker that intercepts requests like:
https://cdn.example.com/packages/session.zip/path/to/asset.pngThe service worker fetches only the byte range needed for the requested ZIP entry, inflates the file when required, caches the extracted response, and returns it to the browser as a normal asset response.
When Is This Package Useful?
Use this package when your app receives a base path and later appends folders and file names to fetch assets from that path.
For example, your app may normally treat the base path as a directory:
const basePath = "https://cdn.example.com/packages/session";
const imageUrl = `${basePath}/slides/slide-1/image.png`;If the same assets are sometimes delivered as a ZIP file instead:
const basePath = "https://cdn.example.com/packages/session.zip";
const imageUrl = `${basePath}/slides/slide-1/image.png`;zip-peek lets the app keep using the same URL-building pattern. The app does not need to know whether the base path points to a normal directory or a ZIP file, and the ZIP-specific work stays inside the service worker.
What It Does
The package provides two pieces:
- A browser API,
initZipPeek(), used by your application. - A standalone service worker file,
zipServiceWorker.js, that must be copied into your app's public output and served by your app. You will find a webpack sample below.
At runtime, the package:
- Registers the ZIP service worker with the configured
workerUrlandscopeUrl. - Reloads once after first service worker install when needed, so the page becomes controlled.
- Intercepts matching
.zip/...asset requests within the service worker scope. - Loads and caches the ZIP central directory on first use, or during init when
allowedZipUrlsis provided. - Fetches only the byte range needed for the requested file.
- Inflates deflated files using
fflate. - Returns the extracted file bytes to the browser request as a normal
Response. - Caches extracted assets in the browser Cache API.
Installation
pnpm add zip-peekor:
npm install zip-peekBasic Usage
import { initZipPeek } from "zip-peek";
const zipPeekInit = await initZipPeek({
workerUrl: new URL("/zipServiceWorker.js", window.location.origin).href,
scopeUrl: new URL("/", window.location.origin).href,
});
if (zipPeekInit.reloaded) {
return;
}After initialization, your app can continue building asset URLs under the ZIP URL:
const zipUrl = "https://cdn.example.com/packages/zipfile.zip";
const imgUrl = `${zipUrl}/slides/slide-1/image.png`;
// This request will work normally and zip-peek will do the magic in the background:
fetch(`${imgUrl}`);The browser requests that URL normally. The service worker intercepts it and serves slides/slide-1/image.png from inside the ZIP.
API
initZipPeek(options)
type InitZipPeekOptions = {
workerUrl: string;
scopeUrl: string;
reloadOnFirstInstall?: boolean;
cacheClearingStrategy?: ZipCacheClearingStrategy;
allowedZipUrls?: string[];
zipAssetCacheName?: string;
assetCacheTtlMs?: number;
logPrefix?: string;
};
type ZipCacheClearingStrategy = "keep-all" | "keep-allowed-urls" | "clear-all";Returns:
type InitZipPeekResult = {
reloaded: boolean;
initialized: boolean;
};Options
workerUrl
The URL where the browser can download the service worker JavaScript file. The service worker file must be served from the same origin as the page.
Example:
workerUrl: new URL("./zipServiceWorker.js", window.location.href).href;In production, you will usually prefer hashed file to give it a long Cache-Control duration:
zipServiceWorker.a1b2c3d4.jsscopeUrl
The service worker scope. The worker can only intercept requests inside this scope. See Service Worker API for more details.
Examples:
scopeUrl: new URL("/", window.location.origin).href;
scopeUrl: new URL("/app/", window.location.origin).href;Choose the smallest scope that includes the pages and asset requests you want the service worker to intercept. For local development, the scope often differs from production because the app may be served from a different path.
reloadOnFirstInstall
Defaults to true. When a service worker is installed for the first time, the current page may not yet be controlled by it. With this option enabled, the package reloads once and returns { reloaded: true, initialized: false }.
cacheClearingStrategy
Defaults to 'keep-all'. Controls which zip-peek cache entries are cleared during initialization.
'keep-all': do not clear cached ZIP manifests or extracted assets.'keep-allowed-urls': requires non-emptyallowedZipUrls; clears cached ZIP data for URLs outside the allow-list.'clear-all': clears all zip-peek cached manifests and extracted assets for a fresh start.
allowedZipUrls
Restricts zip-peek to a known set of ZIP package URLs. When omitted, zip-peek lazily serves any matching .zip/... request inside the service worker scope. When provided, the array must contain at least one ZIP URL, and zip-peek transparently passes matching requests for ZIP URLs outside the list.
The allow-list also acts as a warmup list: zip-peek loads and caches each allowed ZIP manifest during initialization.
Examples:
allowedZipUrls: ["https://cdn.example.com/packages/zipfile.zip"];
allowedZipUrls: [
"https://d3jfe.cloudfront.net/somedir/zipfile.zip?Expires=1798761599&Signature=K2CJW4Z7B3DCM4&Key-Pair-Id=K2CJW4Z7B3DCM4",
];zipAssetCacheName
Overrides the browser Cache API bucket used by the service worker for ZIP manifests and extracted assets.
Default:
zip-cache-v1assetCacheTtlMs
Controls how long extracted assets remain valid in the Cache API.
Default:
2 * 60 * 60 * 1000; // 2 hourslogPrefix
Changes the service worker log prefix.
Default:
[zipSW]Required Server Configuration
1. The ZIP server must support range requests
The ZIP package URL must support HTTP byte range requests. The package reads the ZIP central directory and individual files using Range headers.
The ZIP server should return:
Accept-Ranges: bytesFor range requests, it must return:
HTTP/1.1 206 Partial Content
Content-Range: bytes start-end/totalIf the ZIP server does not support range requests, zip-peek cannot stream individual files.
2. The service worker file must be same-origin
Browsers require service worker scripts to be served from the same origin as the page.
Valid when the page is also served from https://app.example.com:
https://app.example.com/zipServiceWorker.a1b2c3d4.jsInvalid if the page is on another origin:
https://static-assets.example.com/zipServiceWorker.a1b2c3d4.js3. Configure Service-Worker-Allowed
By default, a service worker can only control pages under its own directory. If the worker file is served from a nested folder but needs to control a wider scope, the response for the worker file must include Service-Worker-Allowed.
For an app under /app/, configure:
Service-Worker-Allowed: /app/If the worker needs to control the full origin, configure:
Service-Worker-Allowed: /The header must be returned on the zipServiceWorker.js or zipServiceWorker.<hash>.js response itself.
4. Scope and worker location must match
The registered scope must be allowed by both:
- the worker file location
- the
Service-Worker-Allowedheader
Example:
navigator.serviceWorker.register("/app/zipServiceWorker.a1b2c3d4.js", {
scope: "/app/",
});This requires:
Service-Worker-Allowed: /app/or:
Service-Worker-Allowed: /Bundler Configuration
The package ships the worker as:
zip-peek/zipServiceWorker.jsYour application must copy this file into its public build output. The browser registers service workers by URL, so the worker must exist as a real served JavaScript file.
Production with a hashed worker filename
When your app emits hashed assets, copy the worker with a content hash and pass the final worker URL to initZipPeek().
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{
from: require.resolve("zip-peek/zipServiceWorker.js"),
to: "zipServiceWorker.[contenthash:8].js",
},
],
}),
],
};You then need a way for your runtime code to know the emitted hashed filename. Common approaches include:
- generating an asset manifest
- injecting the filename at build time
- using your framework or bundler's asset URL mechanism
For example, after resolving the emitted filename:
await initZipPeek({
workerUrl: new URL(
`/assets/${zipServiceWorkerFilename}`,
window.location.origin,
).href,
scopeUrl: new URL("/", window.location.origin).href,
});where zipServiceWorkerFilename is the final emitted file name, such as:
zipServiceWorker.a1b2c3d4.jsDevelopment configuration
In development, it is usually simpler to copy the worker without a hash:
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{
from: require.resolve("zip-peek/zipServiceWorker.js"),
to: "zipServiceWorker.js",
},
],
}),
],
};Then use:
await initZipPeek({
workerUrl: new URL("/zipServiceWorker.js", window.location.origin).href,
scopeUrl: new URL("/", window.location.origin).href,
});If your app is served from a subpath, adjust both workerUrl and scopeUrl to match that path.
Caching Behavior
The service worker stores:
- the parsed ZIP manifest
- extracted full assets
The default cache bucket is:
zip-cache-v1Extracted assets expire after two hours by default. Set assetCacheTtlMs to customize this.
Use cacheClearingStrategy to control initialization-time cleanup. keep-all leaves existing entries untouched, keep-allowed-urls removes cached ZIP data outside allowedZipUrls, and clear-all removes all zip-peek cached ZIP data before warming any allowed manifests.
Internal Flow
For a detailed explanation of how the service worker intercepts requests, parses ZIP files, and handles caching, see the Zip Service Worker Flow documentation.
Limitations
- The ZIP URL must end with
.zip. - The ZIP server must support HTTP range requests.
- The service worker file must be same-origin.
- The service worker can only intercept requests inside its registered scope.
- Supported ZIP compression methods are stored (
0) and deflated (8). - ZIP64 is not currently supported.
- The package is browser-only and depends on Service Worker, Cache API, and HTTP range request support.
