vite-compatible-readable-stream
vite-compatible-readable-stream is a browser-oriented fork of readable-stream 3.6. It exposes the familiar Node Readable, Writable, Duplex, Transform, PassThrough, pipeline, and finished APIs, but rearranges circular imports that caused Rollup and Vite production builds to fail. The README names vite-plugin-shim-react-pdf as its expected consumer, so this is best understood as a compatibility patch for older Node-style dependencies, not a new stream design for application code.
Install this only to solve the specific Rollup or Vite circular-dependency problem described by its README, especially in the react-pdf shim path. For new code, native Web Streams in browsers or node:stream in Node are clearer and more current choices.
Use it if
- A Vite production build fails on readable-stream circular dependencies and the package causing it cannot be upgraded
- You use vite-plugin-shim-react-pdf, whose compatibility path explicitly expects this fork
- Browser code must satisfy a dependency that imports Node stream classes and changing that dependency is outside your control
- You need readable-stream 3 semantics specifically, including Readable.from, async iteration, pipeline, and finished
- You are writing new browser code: the platform ReadableStream and WritableStream APIs are native, while this package emulates Node streams and adds three runtime dependencies
- You only target Node: readable.js can delegate to core stream when READABLE_STREAM=disable, but installing a browser compatibility fork gives you no useful advantage over node:stream
- You need current Node stream behavior: this fork tracks readable-stream 3.6.0-era code, not the stream implementation shipped by current Node releases
- You expect TypeScript types in the package: version 3.6.1 declares no types entry and ships no first-party declaration file
- Active maintenance is a requirement: version 3.6.1 and the repository's last code push both date to April 2022, and the README contains only a short explanation of the fork
Setup reality
Installation is a plain npm install, with no native build, credentials, or configuration file. The surprise is that this package is rarely a direct replacement you import deliberately. It exists so a Vite dependency graph that expects readable-stream can be redirected to a circular-dependency-free fork, often through vite-plugin-shim-react-pdf or an alias in Vite's resolve configuration. The package is CommonJS, has no bundled TypeScript declarations, and depends on inherits, string_decoder, and util-deprecate. Its browser map disables util and worker_threads and swaps several internal modules, so compatibility is intentionally narrower than running Node itself. The public surface follows readable-stream 3, including callback-based pipeline and finished rather than the newer promise helpers from node:stream/promises. It also buffers according to Node stream highWaterMark rules, which can surprise browser developers expecting WHATWG streams. Do not mix Node streams and native Web Streams as if they were the same objects; conversion helpers from modern Node are not part of this fork. If the original Vite failure disappears after upgrading the problem dependency, remove this shim instead of making it permanent infrastructure.
Patterns
Import the CommonJS stream classesimport-stream-classes
const { Readable, Writable, Transform, PassThrough } = require('vite-compatible-readable-stream');Version 3.6.1 is CommonJS. In ESM projects, test your bundler's CommonJS interop rather than assuming named imports work identically everywhere.
Create a custom readable streamcreate-readable
const { Readable } = require('vite-compatible-readable-stream');
const source = new Readable({
read() {
this.push('first\n');
this.push('second\n');
this.push(null);
},
});push(null) signals end of stream. _read can be called more than once, so real producers need state that prevents duplicate chunks.
Build a readable from an iterableread-from-iterable
const { Readable } = require('vite-compatible-readable-stream');
const source = Readable.from(['alpha', 'beta', 'gamma']);Readable.from is present in this fork, but browser behavior comes from its substituted from-browser module and should be tested with your target build.
Consume chunks in flowing modeconsume-data-events
source.setEncoding('utf8');
source.on('data', (chunk) => console.log(chunk));
source.on('end', () => console.log('done'));
source.on('error', (err) => console.error(err));Adding a data listener switches the readable into flowing mode. Attach error handling before work starts.
Consume a readable with for awaitconsume-async-iterator
for await (const chunk of source) {
console.log(chunk.toString());
}Async iteration requires Symbol.asyncIterator support in the target runtime and destroys the stream when iteration exits early.
Create a writable sinkcreate-writable
const { Writable } = require('vite-compatible-readable-stream');
const sink = new Writable({
write(chunk, encoding, callback) {
console.log(chunk.toString());
callback();
},
});Call callback exactly once for every chunk. Delaying it is how a writable applies backpressure.
Transform chunkscreate-transform
const { Transform } = require('vite-compatible-readable-stream');
const upper = new Transform({
transform(chunk, encoding, callback) {
callback(null, chunk.toString().toUpperCase());
},
});Pass an error as the first callback argument to fail the stream; thrown async errors are not converted automatically.
Process JavaScript values in object modeuse-object-mode
const { Transform } = require('vite-compatible-readable-stream');
const pickId = new Transform({
objectMode: true,
transform(user, encoding, callback) {
callback(null, user.id);
},
});Object mode counts buffered objects rather than bytes, and its default highWaterMark differs from byte streams.
Pipe a source through a transformpipe-streams
source
.pipe(upper)
.pipe(sink);pipe manages backpressure, but a destination error does not reliably tear down every stream. Use pipeline for multi-stage production work.
Connect streams with cleanuppipeline-with-callback
const { pipeline } = require('vite-compatible-readable-stream');
pipeline(source, upper, sink, (err) => {
if (err) console.error('pipeline failed', err);
else console.log('pipeline complete');
});This package exports the callback form. It does not include node:stream/promises, so omitting the callback is not a promise API.
Observe stream completionwait-for-finish
const { finished } = require('vite-compatible-readable-stream');
finished(sink, (err) => {
if (err) console.error(err);
else console.log('sink closed cleanly');
});finished reports premature close and errors as well as normal completion; keep the cleanup function it returns if the observer may be cancelled.
Stop a stream with an errordestroy-on-error
const err = new Error('input rejected');
source.destroy(err);
source.on('error', (cause) => {
console.error(cause.message);
});An error passed to destroy is emitted asynchronously. Without an error listener, it can become an uncaught exception.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| readable-stream | npm | You need the maintained userland mirror of Node streams and your bundler handles its module graph correctly |
| stream-browserify | npm | A browser bundle needs the conventional Browserify shim for Node's stream module |
| web-streams-polyfill | npm | You want WHATWG Web Streams semantics on browsers or runtimes that lack them |
| vite-plugin-node-polyfills | npm | A Vite app needs several Node core shims, not just a readable-stream workaround |