ZipURL is a lightweight JavaScript text compression library that automatically selects the shortest representation from multiple compression strategies.
Instead of relying on a single compression algorithm, ZipURL tries several encoders, compares the resulting string lengths, and stores the shortest result together with a compact two-character algorithm identifier.
- Multiple compression algorithms in one library
- Automatic selection of the shortest encoded result
- Support for ordinary text
- Specialized JSON compression
- Hybrid compression pipelines
- Prefix preservation
- Simple
compress()/decompress()API - ES module support
- No runtime dependencies
| ID | Method | Description |
|---|---|---|
RW |
Raw | Original uncompressed text |
RL |
RLE | Run-Length Encoding |
HF |
Huffman | Frequency-based variable-length coding |
LZ |
LZ77 | Sliding-window dictionary compression |
MX |
Huffman + LZ77 | LZ77 followed by Huffman coding |
JP |
JSON | JSON key deduplication and structural packing |
JH |
JSON + Huffman | JSON packing followed by Huffman |
JL |
JSON + LZ77 | JSON packing followed by LZ77 |
JM |
JSON + Huffman + LZ77 | JSON packing followed by the hybrid compressor |
For every input, ZipURL generates the applicable representations and selects the one with the smallest data.length.
The main compress() function follows this process:
Input
│
├── Raw
├── RLE
├── Huffman
├── LZ77
├── Huffman + LZ77
│
└── If input is valid JSON
├── JSON packing
├── JSON + Huffman
├── JSON + LZ77
└── JSON + Huffman + LZ77
│
▼
Compare encoded lengths
│
▼
Select shortest
│
▼
Add 2-character ID
│
▼
Output
The selected compression method is stored as a two-character prefix, allowing decompress() to determine which decoder should be used.
ZipURL has no runtime dependencies. The package includes tsup, typescript, and vitest as development dependencies.
npm install zipurlThen import the package:
import { compress, decompress } from "zipurl";You can install the package directly from the GitHub repository without publishing it to the npm Registry:
npm install github:citycafe578/ZipURLThe package name remains zipurl, so the import is the same:
import { compress, decompress } from "zipurl";The repository must include the generated dist/ directory for this installation method. Before pushing changes, run:
npm run build
git add dist
git commit -m "build library"
git pushThe browser bundle is available from jsDelivr after dist/index.global.js has been pushed to GitHub:
<!-- Load the library from GitHub through jsDelivr -->
<script src="https://cdn.jsdelivr.net/gh/citycafe578/ZipURL@main/dist/index.global.js"></script>
<script>
const compressed = ZipURL.compress("Hello World!");
console.log("Compressed:", compressed);
console.log("Decompressed:", ZipURL.decompress(compressed));
</script>The global browser bundle exposes the ZipURL object with compress() and decompress() methods.
Native browser ES modules can import the ESM build directly:
<script type="module">
import { compress, decompress } from "https://cdn.jsdelivr.net/gh/citycafe578/ZipURL@main/dist/index.js";
const compressed = compress("Hello World!");
console.log("Compressed:", compressed);
console.log("Decompressed:", decompress(compressed));
</script>For Vue, React, or Vite projects, installing from npm or GitHub is generally the more reliable option because the bundler can resolve the package normally.
import { compress } from "zipurl";
const text = "AAAAABBBBBCCCCCCCCCCCC";
const compressed = compress(text);
console.log(compressed);import { decompress } from "zipurl";
const restored = decompress(compressed);
console.log(restored);The following property should hold for valid inputs:
decompress(compress(text)) === textcompress() accepts an optional prefix argument.
const compressed = compress("hello world", "https://example.com/");When a prefix is supplied, ZipURL stores it separately from the compressed payload:
P<prefix length>:<prefix><compression ID><compressed data>
For example, the structure is conceptually:
P20:https://example.com/...
During decompression, ZipURL removes the stored prefix before decoding the payload.
This makes it possible to preserve a URL-like prefix while still compressing the remaining content.
Run-Length Encoding replaces consecutive identical characters with a count and the character.
Example:
AAAAABB
becomes conceptually:
5#A2#B
RLE is especially useful when the input contains long runs of repeated characters.
ZipURL builds a frequency map for the input and constructs a Huffman tree.
The resulting character-to-bit mapping is stored together with the packed bit stream.
The encoded representation contains:
<bit length>;<JSON code table>|<packed data>
The bit stream is packed into bytes before being converted into a compact string representation.
The LZ77 implementation uses a sliding window and searches previous input for the longest matching sequence.
The default parameters are:
windowSize = 255
minMatch = 3The hybrid compressor uses:
windowSize = 4096
minMatch = 4Matches are represented using:
(offset,length)
A special (0,1) representation is used when the literal character itself is (.
The hybrid compressor first applies LZ77 and then Huffman coding:
Huffman.encode(LZ77.encode(text, 4096, 4))Decompression reverses the order:
LZ77.decode(Huffman.decode(compressedText))ZipURL has a specialized JSON packer that attempts to reduce structural redundancy before applying the normal compressors.
For objects, property names are stored once in a shared key list and replaced by numeric indexes.
For arrays containing multiple objects, common keys can be represented as a column-like structure:
[
keyList,
packedData
]
This is particularly useful for JSON containing many objects with repeated property names.
For valid JSON, ZipURL tests:
JSON packing
JSON + Huffman
JSON + LZ77
JSON + Huffman + LZ77
The shortest result is selected together with the corresponding ID.
If the input is not valid JSON, these JSON-specific candidates are simply skipped.
A normal compressed result has the following structure:
<ID><data>
where <ID> is one of:
RW
RL
HF
LZ
MX
JP
JH
JL
JM
For example:
HF<encoded data>
The first two characters tell decompress() which decoding pipeline should be used.
When a prefix is provided:
P<prefix length>:<prefix><ID><data>
This allows the decompressor to recover the prefix and then decode the compressed payload.
Compresses the supplied text and automatically selects the shortest supported representation.
const result = compress(text);Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
text |
string |
— | Text to compress |
prefix |
string |
"" |
Optional prefix preserved outside the compressed payload |
Returns:
stringIf text is empty or falsy, an empty string is returned.
Restores the original text.
const text = decompress(compressedText);Parameters:
| Parameter | Type | Description |
|---|---|---|
compressedText |
string |
Output generated by compress() |
Returns:
stringZipURL does not assume that one compression algorithm is always optimal.
Different data patterns favor different approaches:
Repeated characters → RLE
Frequency-heavy text → Huffman
Repeated substrings → LZ77
Mixed redundancy → Huffman + LZ77
Structured JSON → JSON packing
JSON + repeated patterns → JSON + compression
Therefore, ZipURL treats compression as a selection problem:
Generate candidates
↓
Measure encoded length
↓
Choose shortest representation
↓
Store algorithm ID
This approach prioritizes practical output size over committing to a single compression technique.
The library source and package files are organized as follows:
src/
├── index.js # Library entry point and compression implementation
└── index.d.ts # Public TypeScript declarations
scripts/
└── copy-types.mjs # Copies declarations into dist during build
test/
└── library.test.js
dist/ # Generated build output, committed for CDN/GitHub installs
- The current implementation compares candidates using JavaScript string
.length, so the selection criterion is encoded string length rather than a formal byte-level storage measurement. - Huffman output contains its code table as part of the encoded representation.
- JSON compression is only attempted when the input can be parsed successfully by
JSON.parse(). - Compression does not guarantee that the result will be shorter than the original input. The raw representation (
RW) is included so that the shortest candidate can still be selected. - This project is intended as a lightweight JavaScript compression experiment rather than a replacement for established binary compression formats such as gzip, Brotli, or Zstandard.
import { compress, decompress } from "zipurl";
const original = JSON.stringify([
{ name: "Alice", age: 20, city: "Taipei" },
{ name: "Bob", age: 21, city: "Taipei" },
{ name: "Charlie", age: 22, city: "Taipei" }
]);
const compressed = compress(original);
const restored = decompress(compressed);
console.log("Original :", original.length);
console.log("Compressed:", compressed.length);
console.log("Restored :", restored);
console.log("Valid :", restored === original);The package metadata declares the MIT license. Add a LICENSE file containing the MIT license text before publishing publicly.