Skip to content

Commit 5d65878

Browse files
committed
Write better documentation on unarchiving
1 parent e6d13d9 commit 5d65878

File tree

5 files changed

+185
-94
lines changed

5 files changed

+185
-94
lines changed

README.md

Lines changed: 34 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
## Introduction
66

7-
A set of dependency-free JavaScript modules to handle binary data in JS (using Typed Arrays). Includes:
7+
A set of dependency-free JavaScript modules to handle binary data in JS (using
8+
[Typed Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)).
9+
Includes:
810

911
* bitjs/archive: Unarchiving files (unzip, unrar, untar) in JavaScript,
1012
implemented as Web Workers where supported, and allowing progressive
@@ -18,7 +20,7 @@ A set of dependency-free JavaScript modules to handle binary data in JS (using T
1820

1921
## Installation
2022

21-
Install it using your favourite package manager, the package is registered under `@codedread/bitjs`.
23+
Install it using your favourite package manager, the package is registered under `@codedread/bitjs`.
2224
```bash
2325
npm install @codedread/bitjs
2426
```
@@ -29,10 +31,11 @@ yarn add @codedread/bitjs
2931

3032
### Using in Node
3133

32-
This module is an ES Module, which should work as expected in other projects using ES Modules. However,
33-
if you are using a project that uses CommonJs modules, it's a little tricker to use. One valid example
34-
of this is if a TypeScript project compiles to CommonJS, it will try to turn imports into require()
35-
statements, which will break. The fix for this (unfortunately) is to update your tsconfig.json:
34+
This module is an ES Module, which should work as expected in other projects using ES Modules.
35+
However, if you are using a project that uses CommonJs modules, it's a little tricker to use. One
36+
example of this is if a TypeScript project compiles to CommonJS, it will try to turn imports into
37+
require() statements, which will break. The fix for this (unfortunately) is to update your
38+
tsconfig.json:
3639

3740
```json
3841
"moduleResolution": "Node16",
@@ -48,93 +51,24 @@ const { getFullMIMEString } = await import('@codedread/bitjs');
4851

4952
### bitjs.archive
5053

51-
This package includes objects for unarchiving binary data in popular archive formats (zip, rar, tar) providing unzip, unrar and untar capabilities via JavaScript in the browser. A prototype version of a compressor that creates Zip files is also present. The decompression/compression actually happens inside a Web Worker, when the runtime supports it (browsers, deno).
54+
This package includes objects for unarchiving binary data in popular archive formats (zip, rar, tar).
55+
Here is a simple example of unrar:
5256

5357
#### Decompressing
5458

5559
```javascript
56-
import { Unzipper } from './bitjs/archive/decompress.js';
57-
const unzipper = new Unzipper(zipFileArrayBuffer);
58-
unzipper.addEventListener('progress', updateProgress);
59-
unzipper.addEventListener('extract', receiveOneFile);
60-
unzipper.addEventListener('finish', displayZipContents);
61-
unzipper.start();
62-
63-
function updateProgress(e) {
64-
// e.currentFilename is the file currently being unarchived/scanned.
65-
// e.totalCompressedBytesRead has how many bytes have been unzipped so far
66-
}
67-
68-
function receiveOneFile(e) {
69-
// e.unarchivedFile.filename: string
70-
// e.unarchivedFile.fileData: Uint8Array
71-
}
72-
73-
function displayZipContents() {
74-
// Now sort your received files and show them or whatever...
75-
}
76-
```
77-
78-
The unarchivers also support progressively decoding while streaming the file, if you are receiving the zipped file from a slow place (a Cloud API, for instance). For example:
79-
80-
```javascript
81-
import { Unzipper } from './bitjs/archive/decompress.js';
82-
const unzipper = new Unzipper(anArrayBufferWithStartingBytes);
83-
unzipper.addEventListener('progress', updateProgress);
84-
unzipper.addEventListener('extract', receiveOneFile);
85-
unzipper.addEventListener('finish', displayZipContents);
86-
unzipper.start();
87-
...
88-
// after some time
89-
unzipper.update(anArrayBufferWithMoreBytes);
90-
...
91-
// after some more time
92-
unzipper.update(anArrayBufferWithYetMoreBytes);
93-
```
94-
95-
##### A NodeJS Example
96-
97-
```javascript
98-
import * as fs from 'fs';
99-
import { getUnarchiver } from './archive/decompress.js';
100-
101-
const nodeBuffer = fs.readFileSync('comic.cbz');
102-
const ab = nodeBuffer.buffer.slice(nodeBuffer.byteOffset, nodeBuffer.byteOffset + nodeBuffer.length);
103-
const unarchiver = getUnarchiver(ab);
104-
unarchiver.addEventListener('progress', () => process.stdout.write('.'));
105-
unarchiver.addEventListener('extract', (evt) => {
106-
const extractedFile = evt.unarchivedFile;
107-
console.log(`${extractedFile.filename} (${extractedFile.fileData.byteLength} bytes)`);
108-
});
109-
unarchiver.addEventListener('finish', () => console.log(`Done!`));
110-
unarchiver.start();
60+
import { Unrarrer } from './bitjs/archive/decompress.js';
61+
const unrar = new Unrarrer(rarFileArrayBuffer);
62+
unrar.addEventListener('extract', (e) => {
63+
const {filename, fileData} = e.unarchivedFile;
64+
console.log(`Extracted ${filename} (${fileData.byteLength} bytes)`);
65+
// Do something with fileData...
66+
});
67+
unrar.addEventListener('finish', () => console.log('Done'));
68+
unrar.start();
11169
```
11270

113-
#### Compressing
114-
115-
The Zipper only supports creating zip files without compression (store only) for now. The interface
116-
is pretty straightforward and there is no event-based / streaming API.
117-
118-
```javascript
119-
import { Zipper } from './bitjs/archive/compress.js';
120-
const zipper = new Zipper();
121-
const now = Date.now();
122-
// Zip files foo.jpg and bar.txt.
123-
const zippedArrayBuffer = await zipper.start(
124-
[
125-
{
126-
fileName: 'foo.jpg',
127-
lastModTime: now,
128-
fileData: fooArrayBuffer,
129-
},
130-
{
131-
fileName: 'bar.txt',
132-
lastModTime: now,
133-
fileData: barArrayBuffer,
134-
}
135-
],
136-
true /* isLastFile */);
137-
```
71+
More explanation and examples are located on [the API page](./docs/bitjs.archive.md).
13872

13973
### bitjs.codecs
14074

@@ -164,7 +98,8 @@ exec(cmd, (error, stdout) => {
16498

16599
### bitjs.file
166100

167-
This package includes code for dealing with files. It includes a sniffer which detects the type of file, given an ArrayBuffer.
101+
This package includes code for dealing with files. It includes a sniffer which detects the type of
102+
file, given an ArrayBuffer.
168103

169104
```javascript
170105
import { findMimeType } from './bitjs/file/sniffer.js';
@@ -173,7 +108,8 @@ const mimeType = findMimeType(someArrayBuffer);
173108

174109
### bitjs.image
175110

176-
This package includes code for dealing with binary images. It includes a module for converting WebP images into alternative raster graphics formats (PNG/JPG).
111+
This package includes code for dealing with binary images. It includes a module for converting WebP
112+
images into alternative raster graphics formats (PNG/JPG).
177113

178114
```javascript
179115
import { convertWebPtoPNG, convertWebPtoJPG } from './bitjs/image/webp-shim/webp-shim.js';
@@ -188,7 +124,8 @@ convertWebPtoPNG(webpBuffer).then(pngBuf => {
188124

189125
### bitjs.io
190126

191-
This package includes stream objects for reading and writing binary data at the bit and byte level: BitStream, ByteStream.
127+
This package includes stream objects for reading and writing binary data at the bit and byte level:
128+
BitStream, ByteStream.
192129

193130
```javascript
194131
import { BitStream } from './bitjs/io/bitstream.js';
@@ -199,8 +136,13 @@ const flagbits = bstream.peekBits(6); // look ahead at next 6 bits, but do not a
199136

200137
## Reference
201138

202-
* [UnRar](http://codedread.github.io/bitjs/docs/unrar.html): A work-in-progress description of the RAR file format.
139+
* [UnRar](http://codedread.github.io/bitjs/docs/unrar.html): A work-in-progress description of the
140+
RAR file format.
203141

204142
## History
205143

206-
This project grew out of another project of mine, [kthoom](https://github.com/codedread/kthoom) (a comic book reader implemented in the browser). This repository was automatically exported from [my original repository on GoogleCode](https://code.google.com/p/bitjs) and has undergone considerable changes and improvements since then, including adding streaming support, starter RarVM support, tests, many bug fixes, and updating the code to ES6.
144+
This project grew out of another project of mine, [kthoom](https://github.com/codedread/kthoom) (a
145+
comic book reader implemented in the browser). This repository was automatically exported from
146+
[my original repository on GoogleCode](https://code.google.com/p/bitjs) and has undergone
147+
considerable changes and improvements since then, including adding streaming support, starter RarVM
148+
support, tests, many bug fixes, and updating the code to modern JavaScript and supported features.

archive/events.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export class UnarchiveFinishEvent extends UnarchiveEvent {
107107
}
108108
}
109109

110+
// TODO(bitjs): Fully document these. They are confusing.
110111
/**
111112
* Progress event.
112113
*/

docs/bitjs.archive.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# bitjs.archive
2+
3+
This package includes objects for unarchiving binary data in popular archive formats (zip, rar, tar)
4+
providing unzip, unrar and untar capabilities via JavaScript in the browser or various JavaScript
5+
runtimes (node, deno, bun).
6+
7+
A prototype version of a compressor that creates Zip files is also present. The decompression /
8+
compression happens inside a Web Worker, if the runtime supports it (browsers, deno).
9+
10+
The API is event-based, you will want to subscribe to some of these events:
11+
* 'progress': Periodic updates on the progress (bytes processed).
12+
* 'extract': Sent whenever a single file in the archive was fully decompressed.
13+
* 'finish': Sent when decompression/compression is complete.
14+
15+
## Decompressing
16+
17+
### Simple Example of unzip
18+
19+
Here is a simple example of unzipping a file. It is assumed the zip file exists as an
20+
[`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer),
21+
which you can get via
22+
[`XHR`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Sending_and_Receiving_Binary_Data),
23+
from a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/arrayBuffer),
24+
[`Fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Response/arrayBuffer),
25+
[`FileReader`](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsArrayBuffer),
26+
etc.
27+
28+
```javascript
29+
import { Unzipper } from './bitjs/archive/decompress.js';
30+
const unzipper = new Unzipper(zipFileArrayBuffer);
31+
unzipper.addEventListener('extract', (evt) => {
32+
const {filename, fileData} = evt.unarchivedFile;
33+
console.log(`unzipped ${filename} (${fileData.byteLength} bytes)`);
34+
// Do something with fileData...
35+
});
36+
unzipper.addEventListener('finish', () => console.log(`Finished!`));
37+
unzipper.start();
38+
```
39+
40+
`start()` is an async method that resolves a `Promise` when the unzipping is complete, so you can
41+
`await` on it, if you need to.
42+
43+
### Progressive unzipping
44+
45+
The unarchivers also support progressively decoding while streaming the file, if you are receiving
46+
the zipped file from a slow place (a Cloud API, for instance). Send the first `ArrayBuffer` in the
47+
constructor, and send subsequent `ArrayBuffers` using the `update()` method.
48+
49+
```javascript
50+
import { Unzipper } from './bitjs/archive/decompress.js';
51+
const unzipper = new Unzipper(anArrayBufferWithStartingBytes);
52+
unzipper.addEventListener('extract', () => {...});
53+
unzipper.addEventListener('finish', () => {...});
54+
unzipper.start();
55+
...
56+
// after some time
57+
unzipper.update(anArrayBufferWithMoreBytes);
58+
...
59+
// after some more time
60+
unzipper.update(anArrayBufferWithYetMoreBytes);
61+
```
62+
63+
### getUnarchiver()
64+
65+
If you don't want to bother with figuring out if you have a zip, rar, or tar file, you can use the
66+
convenience method `getUnarchiver()`, which sniffs the bytes for you and creates the appropriate
67+
unarchiver.
68+
69+
```javascript
70+
import { getUnarchiver } from './bitjs/archive/decompress.js';
71+
const unarchiver = getUnarchiver(anArrayBuffer);
72+
unarchive.addEventListener('extract', () => {...});
73+
// etc...
74+
unarchiver.start();
75+
```
76+
77+
### Non-Browser JavaScript Runtime Examples
78+
79+
The API works in other JavaScript runtimes too (Node, Deno, Bun).
80+
81+
#### NodeJS
82+
83+
```javascript
84+
import * as fs from 'fs';
85+
import { getUnarchiver } from './archive/decompress.js';
86+
87+
const nodeBuf = fs.readFileSync('comic.cbz');
88+
// NOTE: Small files may not have a zero byte offset in Node, so we slice().
89+
// See https://nodejs.org/api/buffer.html#bufbyteoffset.
90+
const ab = nodeBuf.buffer.slice(nodeBuf.byteOffset, nodeBuf.byteOffset + nodeBuf.length);
91+
const unarchiver = getUnarchiver(ab);
92+
unarchiver.addEventListener('progress', () => process.stdout.write('.'));
93+
unarchiver.addEventListener('extract', (evt) => {
94+
const {filename, fileData} = evt.unarchivedFile;
95+
console.log(`${filename} (${fileData.byteLength} bytes)`);
96+
});
97+
unarchiver.addEventListener('finish', () => console.log(`Done!`));
98+
unarchiver.start();
99+
```
100+
101+
#### Deno
102+
103+
```typescript
104+
import { UnarchiveExtractEvent } from './archive/events.js';
105+
import { getUnarchiver} from './archive/decompress.js';
106+
107+
const print = (s: string) => Deno.writeAll(Deno.stdout, new TextEncoder().encode(s));
108+
109+
async function go() {
110+
const arr: Uint8Array = await Deno.readFile('example.zip');
111+
const unarchiver = getUnarchiver(arr.buffer);
112+
unarchiver.addEventListener('extract', (evt) => {
113+
const {filename, fileData} = (evt as UnarchiveExtractEvent).unarchivedFile;
114+
print(`\n${filename} (${fileData.byteLength} bytes)\n`);
115+
// Do something with fileData...
116+
});
117+
unarchiver.addEventListener('finish', () => { console.log(`Done!`); Deno.exit(); });
118+
unarchiver.addEventListener('progress', (evt) => print('.'));
119+
unarchiver.start();
120+
}
121+
122+
await go();
123+
```
124+
125+
## Compressing
126+
127+
The Zipper only supports creating zip files without compression (store only) for now. The interface
128+
is pretty straightforward and there is no event-based / streaming API.
129+
130+
```javascript
131+
import { Zipper } from './bitjs/archive/compress.js';
132+
const zipper = new Zipper();
133+
const now = Date.now();
134+
// Create a zip file with files foo.jpg and bar.txt.
135+
const zippedArrayBuffer = await zipper.start(
136+
[
137+
{
138+
fileName: 'foo.jpg',
139+
lastModTime: now,
140+
fileData: fooArrayBuffer,
141+
},
142+
{
143+
fileName: 'bar.txt',
144+
lastModTime: now,
145+
fileData: barArrayBuffer,
146+
}
147+
],
148+
true /* isLastFile */);
149+
```

tests/unzipper-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ async function getFiles(fileChangeEvt) {
2626

2727
for (const b of buffers) {
2828
await new Promise((resolve, reject) => {
29-
const unzipper = new Unzipper(b.buffer, { pathToBitJS: '../' });
29+
const unzipper = new Unzipper(b.buffer);
3030
unzipper.addEventListener(UnarchiveEventType.FINISH, () => {
3131
fileNum++;
3232
resolve();

tests/zipper-test.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ async function getFiles(fileChangeEvt) {
3737
result.innerHTML = `Loaded files`;
3838

3939
const zipper = new Zipper({
40-
pathToBitJS: '../',
4140
zipCompressionMethod: ZipCompressionMethod.DEFLATE,
4241
});
4342
byteArray = await zipper.start(fileInfos, true);

0 commit comments

Comments
 (0)