Skip to content

Commit b5c095d

Browse files
committed
Properly test web streams case, and update README example with fixes
1 parent b23fa7a commit b5c095d

File tree

2 files changed

+103
-17
lines changed

2 files changed

+103
-17
lines changed

README.md

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,50 +77,62 @@ const brotli = await brotliPromise; // Import is async in browsers due to wasm r
7777

7878
const input = 'some input';
7979

80+
// Get a stream for your input:
8081
const inputStream = new ReadableStream({
81-
start (controller) {
82+
start(controller) {
8283
controller.enqueue(input);
8384
controller.close();
8485
}
8586
});
8687

88+
// Convert the streaming data to Uint8Arrays, if necessary:
8789
const textEncoderStream = new TextEncoderStream();
90+
91+
// Create a stream to incrementally compress the data as it streams:
8892
const compressStream = new brotli.CompressStream();
8993
const compressionStream = new TransformStream({
90-
start () {},
91-
transform (chunk, controller) {
92-
controller.enqueue(compressStream.compress(chunk, 100));
94+
start() { },
95+
transform(chunk, controller) {
96+
// Compress data, producing 1024 bytes of output at a time (i.e. limiting the output):
97+
controller.enqueue(compressStream.compress(chunk, 1024));
9398
},
94-
flush (controller) {
95-
if (compressStream.result() === brotli.BrotliStreamResult.NeedsMoreInput) {
96-
controller.enqueue(compressStream.compress(undefined, 100));
99+
flush(controller) {
100+
// Stream remaining compressed data after input finishes, 1024 bytes of output at a time:
101+
while (
102+
compressStream.result() === brotli.BrotliStreamResult.NeedsMoreInput ||
103+
compressStream.result() === brotli.BrotliStreamResult.NeedsMoreOutput
104+
) {
105+
controller.enqueue(compressStream.compress(undefined, 1024));
97106
}
98107
controller.terminate();
99108
}
100109
});
101110

102111
const decompressStream = new brotli.DecompressStream();
103112
const decompressionStream = new TransformStream({
104-
start () {},
105-
transform (chunk, controller) {
106-
controller.enqueue(decompressStream.decompress(chunk, 100));
113+
start() { },
114+
transform(chunk, controller) {
115+
// Decompress, producing 1024 bytes of output at a time:
116+
controller.enqueue(decompressStream.decompress(chunk, 1024));
107117
},
108-
flush (controller) {
109-
if (decompressStream.result() === brotli.BrotliStreamResult.NeedsMoreInput) {
110-
controller.enqueue(decompressStream.decompress(undefined, 100));
118+
flush(controller) {
119+
// Decompress all remaining output after input finishes, 1024 bytes at a time:
120+
while (decompressStream.result() === brotli.BrotliStreamResult.NeedsMoreOutput) {
121+
controller.enqueue(decompressStream.decompress(new Uint8Array(0), 1024));
111122
}
112123
controller.terminate();
113124
}
114125
});
115126

116-
const textDecoderStream = new TextDecoderStream()
127+
const textDecoderStream = new TextDecoderStream();
128+
129+
let output = '';
117130
const outputStream = new WritableStream({
118-
write (chunk) {
131+
write(chunk) {
119132
output += chunk;
120133
}
121134
});
122135

123-
let output = '';
124136
await inputStream
125137
.pipeThrough(textEncoderStream)
126138
.pipeThrough(compressionStream)
@@ -130,7 +142,9 @@ await inputStream
130142
console.log(output); // Prints 'some input'
131143
```
132144

133-
Note that `TransformStream` has become available in all browsers as of mid-2022. https://caniuse.com/mdn-api_transformstream
145+
Note that `TransformStream` has become available in all browsers as of mid-2022: https://caniuse.com/mdn-api_transformstream. It's also been available in Node.js (experimentally) since v16.5.0.
146+
147+
This is a simplified demo example - you may well want to tweak the specific stream buffer sizes for compression/decompression to your use case, to reuse buffers, or explore further optimizations if you're interested in these streaming use cases.
134148

135149
## Alternatives
136150

test/brotli.spec.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,78 @@ describe("Brotli-wasm", () => {
277277
expect(stream.result()).to.equal(brotli.BrotliStreamResult.ResultSuccess);
278278
expect(textDecoder.decode(new Uint8Array([...output1, ...output2]))).to.equal('Brotli brotli brotli brotli');
279279
});
280+
281+
const areStreamsAvailable = !!globalThis.TransformStream;
282+
283+
it("can streamingly compress & decompress back to the original result with web streams", async function () {
284+
if (!areStreamsAvailable) return this.skip();
285+
286+
// This is very similar to the streaming example in the README, but with reduced buffer sizes to
287+
// try and ensure it catches any issues:
288+
289+
let input = "";
290+
for (let i = 0; i < 1000; i++) {
291+
input += `${i}: Brotli brotli brotli brotli `;
292+
}
293+
294+
const inputStream = new ReadableStream({
295+
start(controller) {
296+
controller.enqueue(input);
297+
controller.close();
298+
}
299+
});
300+
301+
const textEncoderStream = new TextEncoderStream();
302+
303+
const compressStream = new brotli.CompressStream();
304+
const compressionStream = new TransformStream({
305+
start() { },
306+
transform(chunk, controller) {
307+
controller.enqueue(compressStream.compress(chunk, 10));
308+
},
309+
flush(controller) {
310+
while (
311+
compressStream.result() === brotli.BrotliStreamResult.NeedsMoreInput ||
312+
compressStream.result() === brotli.BrotliStreamResult.NeedsMoreOutput
313+
) {
314+
controller.enqueue(compressStream.compress(undefined, 10));
315+
}
316+
controller.terminate();
317+
}
318+
});
319+
320+
const decompressStream = new brotli.DecompressStream();
321+
const decompressionStream = new TransformStream({
322+
start() { },
323+
transform(chunk, controller) {
324+
controller.enqueue(decompressStream.decompress(chunk, 100));
325+
},
326+
flush(controller) {
327+
while (decompressStream.result() === brotli.BrotliStreamResult.NeedsMoreOutput) {
328+
controller.enqueue(decompressStream.decompress(new Uint8Array(0), 100));
329+
}
330+
controller.terminate();
331+
}
332+
});
333+
334+
const textDecoderStream = new TextDecoderStream();
335+
336+
let output = '';
337+
const outputStream = new WritableStream({
338+
write(chunk) {
339+
output += chunk;
340+
}
341+
});
342+
343+
await inputStream
344+
.pipeThrough(textEncoderStream)
345+
.pipeThrough(compressionStream)
346+
.pipeThrough(decompressionStream)
347+
.pipeThrough(textDecoderStream)
348+
.pipeTo(outputStream);
349+
350+
expect(output).to.equal(input);
351+
});
280352
});
281353

282354
function generateRandomBytes(size: number) {

0 commit comments

Comments
 (0)