You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue outlines the implementation roadmap for MQTT.js v6, a major release focused on reducing dependencies, improving tree-shakability, reducing browser bundle size, and supporting multiple JavaScript runtimes — while maintaining or improving current performance.
This builds on the discussion in #2002 and depends on mqtt-packet v10 (a zero-dependency, TypeScript-native rewrite).
Transport interface: callbacks or async iterators?
Callbacks
Better performance, less GC pressure, more consistent across runtimes. Worth benchmarking on modern runtimes first.
ws dependency?
Eliminate entirely
Node.js 24+ ships native WebSocket. Keep ws as devDependency for tests only.
Browser bundle format?
ESM only
Drop IIFE bundle. Modern bundlers and browsers all support ESM.
debug dependency?
Keep for now
Slight bias towards keeping it for ecosystem compatibility. Re-evaluate later.
Monorepo vs separate repos?
Monorepo (npm workspaces + Nx)
Each transport published as a separate package, CLI as separate package, all in one repo. Nx for task orchestration, build caching, and release management.
CLI?
Keep in monorepo, publish separately
Very useful debugging tool. Published as @mqttjs/cli, not bundled with the core library.
Transport interface design?
Use Node.js Duplex streams
The original TransportConnection interface was essentially reinventing streams. Transports should return Duplex streams (Node.js native). The write() side must accept both Uint8Array and Buffer without re-wrapping.
Size on disk?
Not a goal
Focus on reducing bundle size and dependency count, not size on disk.
Zero dependencies, TypeScript, Uint8Array-native with Buffer fast-paths
Backward-compatible parser() / generate() / writeToStream() API
P.1 Convert repository to monorepo (npm workspaces + Nx)
Structure:
packages/
mqtt/ → core client library (published as `mqtt`)
mqtt-packet/ → packet codec (published as `mqtt-packet`)
transport-tcp/ → TCP transport (published as `@mqttjs/transport-tcp`)
transport-tls/ → TLS transport (published as `@mqttjs/transport-tls`)
transport-ws/ → WebSocket transport (published as `@mqttjs/transport-ws`)
transport-wx/ → WeChat transport (published as `@mqttjs/transport-wx`)
transport-ali/ → Alibaba IoT transport (published as `@mqttjs/transport-ali`)
transport-socks/ → SOCKS proxy transport (published as `@mqttjs/transport-socks`)
cli/ → CLI tools (published as `@mqttjs/cli`)
npm workspaces for dependency management and linking
Nx (devDependency only — zero impact on published packages) for:
Task graph: understands inter-package dependencies, builds in correct topological order (e.g. mqtt-packet before mqtt, transports in parallel)
Build caching: local + remote via Nx Cloud (free for open source). A PR touching only transport-ws skips rebuilding/testing mqtt-packet, transport-tcp, etc.
Affected commands: nx affected -t test only tests packages impacted by the current changeset — significant CI time savings as the monorepo grows to 9+ packages
Release orchestration: nx release handles versioning, changelogs, and npm publishing across packages with dependency-aware version bumps
Shared tooling: TypeScript, ESLint, test runner configs at root
CI: nx affected -t lint,test,build for PRs, nx run-many -t lint,test,build for main branch
Phase 1: Dependency Cleanup (non-breaking, can ship as v5.x minor/patch)
These changes reduce the dependency count without breaking the public API. They can be shipped incrementally in v5.x releases before the v6 major bump.
1.1 Replace number-allocator with an inline implementation
Current: pulls in js-sdsl (a full data structures library) for allocating message IDs in range 1-65535
Replacement: a simple bitfield-based or array-based allocator (~50-80 lines)
Saves: ~2 dependencies
1.2 Replace worker-timers with a lightweight implementation
Current: pulls in @babel/runtime, tslib, broker-factory, worker-factory, fast-unique-numbers (~10 packages)
The actual need: setTimeout/setInterval that work reliably in web workers (where native timers can be throttled)
Replacement: a focused ~100-line implementation using inline worker blob or native timers with isWebWorker detection
Saves: ~10 dependencies
1.3 Evaluate lru-cache replacement
Current: 844KB on disk (mostly docs/types), used for topic alias send cache
Option: inline ~50-line LRU implementation (doubly-linked list + Map) since the use case is simple (fixed max size, string keys, numeric values)
Alternative: keep it — the runtime code is small and well-tested
Decision: benchmark and decide
1.4 Make socks an optional dependency / plugin
Already stubbed out in browser builds
Most users don't use SOCKS proxies
Will become a separate transport package in the monorepo
Saves: ~4 dependencies
Phase 2: Adopt mqtt-packet v10
2.1 Upgrade to mqtt-packet@10
The v10 compatibility layer should make this a drop-in upgrade
Run full test suite (Node + browser) to validate
This alone eliminates bl, process-nextick-args, and the transitive readable-stream + buffer from mqtt-packet's chain
2.2 Remove @types/readable-stream from production dependencies
Currently listed in dependencies instead of devDependencies
Should be in devDependencies (or unnecessary if readable-stream ships its own types)
2.3 Remove @types/ws from production dependencies
Same issue — should be in devDependencies
Phase 3: Transport Plugin Architecture
This is the core architectural change that enables tree-shaking and multi-runtime support. Each transport is published as a separate package from the monorepo.
The write() side must accept both Uint8Array and Buffer without re-wrapping
Readable side emits Uint8Array or Buffer chunks (both are fine, the parser handles either)
Transport factory signature: (client: MqttClient, opts: IClientOptions) => Duplex
3.2 Extract transports into separate packages
Package
Transport
Notes
@mqttjs/transport-tcp
mqtt://, tcp://
Node.js net.createConnection()
@mqttjs/transport-tls
mqtts://, ssl://
Node.js tls.connect()
@mqttjs/transport-ws
ws://, wss://
Native WebSocket (Node.js 24+ and browser)
@mqttjs/transport-wx
wx://, wxs://
WeChat mini-program
@mqttjs/transport-ali
ali://, alis://
Alibaba IoT
@mqttjs/transport-socks
SOCKS proxy wrapper
Wraps TCP/TLS transports through SOCKS proxy
Note: With Node.js 24+ shipping native WebSocket, the ws npm package is no longer needed as a production dependency. It can remain as a devDependency for tests.
This is the most impactful and most difficult phase. It requires refactoring client.ts (2428 lines) to decouple from the readable-stream polyfill while still using Node.js native streams.
4.1 Refactor MqttClient to use native Node.js streams
Replace readable-stream imports with node:stream
The parser wrapper from mqtt-packet v10 accepts chunks via parse(buf) — no need for a Writable pipe target
Transport streams are native Duplex — no polyfill needed
4.2 Remove BufferedDuplex.ts
Currently wraps browser WebSocket in a Node.js Duplex stream
With the transport architecture, the WebSocket transport handles buffering internally
4.3 Refactor Store to not depend on readable-stream
Currently Store.createStream() returns a Readable from readable-stream
Replace with native node:streamReadable or a callback-based iteration
4.4 Remove readable-stream from dependencies
This eliminates readable-stream, buffer, events, process, abort-controller, event-target-shim, safe-buffer, string_decoder from the dependency tree
Saves: ~8 dependencies and significant bundle size
Node.js 24+ native streams are used directly — no polyfill
4.5 Update esbuild configuration
Remove esbuild-plugin-polyfill-node (no more readable-stream to polyfill)
Remove browser field entries for fs, tls, net (handled by transport plugin architecture)
Ship ESM-only browser bundle (drop IIFE format)
Expected browser bundle: ~80-120 KB minified (down from 368 KB)
Phase 5: CLI as Separate Package
5.1 Extract CLI into @mqttjs/cli package in the monorepo
Moves commist, help-me, minimist, concat-stream, split2 out of the core library's dependency tree
@mqttjs/cli depends on mqtt (the core library)
Provides mqtt, mqtt_pub, mqtt_sub bin commands
Saves: ~8 dependencies for library consumers who don't use the CLI
Phase 6: Multi-Runtime Support
6.1 Verify Deno compatibility
With Uint8Array-native mqtt-packet and no Node.js stream dependency, Deno support should work via npm:mqtt
Add a TCP transport using Deno.connect() (optional, community-driven)
6.2 Verify Bun compatibility
Bun supports Node.js APIs natively, so this should work out of the box
Validate with test suite
6.3 React Native validation
Currently supported via react-native exports condition
Verify the WebSocket transport works with React Native's WebSocket implementation
Phase 7: Testing, Benchmarks & Release
7.1 Full test suite pass
All existing Node.js tests
All existing browser tests (web-test-runner)
New tests for transport plugin registration
New tests for tree-shaking (verify bundle only includes registered transports)
7.2 Performance benchmarks
Compare v6 vs v5 for:
Connection establishment time
Message publish throughput (QoS 0, 1, 2)
Message receive throughput
Memory usage under sustained load
Target: equal or better than v5 on Node.js
7.3 Bundle size analysis
Measure full bundle (all transports)
Measure minimal bundle (WebSocket-only for browser)
Compare against v5
7.4 Migration guide
Document all breaking changes
Provide codemods or migration scripts where possible
Task ordering — npm run build across packages runs in parallel with no dependency awareness. Nx builds mqtt-packet first, then transports + core in parallel, then CLI — automatically.
Build caching — every CI run rebuilds everything from scratch. Nx caches locally and remotely (Nx Cloud, free for OSS). A PR touching only one transport skips everything else.
Change detection — without nx affected, a typo fix in transport-wx triggers the full test suite across all 9 packages.
Release management — coordinating version bumps, changelogs, and publishing across interdependent packages is manual and error-prone. nx release handles this with dependency-aware version bumps.
Nx is a devDependency only — it adds zero weight to published packages and zero impact on consumers.
Phasing & Compatibility
Phase 1 can ship in v5.x minor releases (non-breaking dependency swaps)
Phase 2 can ship as v5.x (mqtt-packet v10 with compat layer is drop-in)
Phases 3-7 are the v6 major release
P.1 (monorepo conversion) should happen early to enable parallel work on transports
This phasing means the community benefits incrementally — dependency reductions in Phase 1-2 land without waiting for the full v6.
Resolved Questions
#
Question
Answer
Source
1
Transport interface: callbacks or async iterators?
Callbacks. Use Node.js Duplex streams — don't reinvent the wheel.
Monorepo with npm workspaces + Nx. Each transport as separate published package. Nx for task orchestration, caching, and releases (devDependency only).
Summary
This issue outlines the implementation roadmap for MQTT.js v6, a major release focused on reducing dependencies, improving tree-shakability, reducing browser bundle size, and supporting multiple JavaScript runtimes — while maintaining or improving current performance.
This builds on the discussion in #2002 and depends on mqtt-packet v10 (a zero-dependency, TypeScript-native rewrite).
Current State (v5.x)
sideEffectsin package.jsonrequire()withisBrowsercheck)Dependency Breakdown
The heaviest chains today:
mqtt-packet→bl→readable-stream+bufferreadable-stream→buffer,events,process,abort-controllerworker-timers→@babel/runtime,tslib,broker-factory,worker-factorysocks→ip-address,smart-bufferconcat-stream,split2,commist,help-me,minimistnumber-allocator→js-sdslTarget State (v6)
sideEffectsfalseDecisions (from discussion)
Based on feedback from @mcollina and @jacoscaz:
wsdependency?WebSocket. Keepwsas devDependency for tests only.debugdependency?@mqttjs/cli, not bundled with the core library.TransportConnectioninterface was essentially reinventing streams. Transports should return Duplex streams (Node.js native). Thewrite()side must accept bothUint8ArrayandBufferwithout re-wrapping.Implementation Roadmap
Prerequisites
P.0 mqtt-packet v10 released (see mqtt-packet v10 roadmap)
Uint8Array-native withBufferfast-pathsparser()/generate()/writeToStream()APIP.1 Convert repository to monorepo (npm workspaces + Nx)
mqtt-packetbeforemqtt, transports in parallel)transport-wsskips rebuilding/testingmqtt-packet,transport-tcp, etc.nx affected -t testonly tests packages impacted by the current changeset — significant CI time savings as the monorepo grows to 9+ packagesnx releasehandles versioning, changelogs, and npm publishing across packages with dependency-aware version bumpsnx affected -t lint,test,buildfor PRs,nx run-many -t lint,test,buildfor main branchnx.json:{ "targetDefaults": { "build": { "dependsOn": ["^build"], "cache": true }, "test": { "dependsOn": ["build"], "cache": true }, "lint": { "cache": true } } }Phase 1: Dependency Cleanup (non-breaking, can ship as v5.x minor/patch)
These changes reduce the dependency count without breaking the public API. They can be shipped incrementally in v5.x releases before the v6 major bump.
1.1 Replace
number-allocatorwith an inline implementationjs-sdsl(a full data structures library) for allocating message IDs in range 1-655351.2 Replace
worker-timerswith a lightweight implementation@babel/runtime,tslib,broker-factory,worker-factory,fast-unique-numbers(~10 packages)setTimeout/setIntervalthat work reliably in web workers (where native timers can be throttled)isWebWorkerdetection1.3 Evaluate
lru-cachereplacement1.4 Make
socksan optional dependency / pluginPhase 2: Adopt mqtt-packet v10
2.1 Upgrade to
mqtt-packet@10bl,process-nextick-args, and the transitivereadable-stream+bufferfrom mqtt-packet's chain2.2 Remove
@types/readable-streamfrom production dependenciesdependenciesinstead ofdevDependenciesdevDependencies(or unnecessary if readable-stream ships its own types)2.3 Remove
@types/wsfrom production dependenciesdevDependenciesPhase 3: Transport Plugin Architecture
This is the core architectural change that enables tree-shaking and multi-runtime support. Each transport is published as a separate package from the monorepo.
3.1 Define the transport contract
write()side must accept bothUint8ArrayandBufferwithout re-wrappingUint8ArrayorBufferchunks (both are fine, the parser handles either)(client: MqttClient, opts: IClientOptions) => Duplex3.2 Extract transports into separate packages
@mqttjs/transport-tcpmqtt://,tcp://net.createConnection()@mqttjs/transport-tlsmqtts://,ssl://tls.connect()@mqttjs/transport-wsws://,wss://WebSocket(Node.js 24+ and browser)@mqttjs/transport-wxwx://,wxs://@mqttjs/transport-aliali://,alis://@mqttjs/transport-socks3.3 Update package.json exports map
{ ".": { "default": "..." }, "./client": "./build/lib/client.js", "./transports/tcp": { "default": "./build/lib/transports/tcp.js" }, "./transports/tls": { "default": "./build/lib/transports/tls.js" }, "./transports/ws": { "default": "./build/lib/transports/ws.js" } }3.4 Add
"sideEffects": falseto package.jsonprocess.nextTick = setImmediateside effect inconnect/index.ts(move to client init)3.5 Maintain backward-compatible
connect()/connectAsync()mqttpackage import auto-registers all transports for the current environment (same as today)Phase 4: Remove
readable-streamfrom MQTT.js CoreThis is the most impactful and most difficult phase. It requires refactoring
client.ts(2428 lines) to decouple from thereadable-streampolyfill while still using Node.js native streams.4.1 Refactor
MqttClientto use native Node.js streamsreadable-streamimports withnode:streamparse(buf)— no need for aWritablepipe target4.2 Remove
BufferedDuplex.ts4.3 Refactor
Storeto not depend onreadable-streamStore.createStream()returns aReadablefromreadable-streamnode:streamReadableor a callback-based iteration4.4 Remove
readable-streamfrom dependenciesreadable-stream,buffer,events,process,abort-controller,event-target-shim,safe-buffer,string_decoderfrom the dependency tree4.5 Update esbuild configuration
esbuild-plugin-polyfill-node(no morereadable-streamto polyfill)browserfield entries forfs,tls,net(handled by transport plugin architecture)Phase 5: CLI as Separate Package
@mqttjs/clipackage in the monorepocommist,help-me,minimist,concat-stream,split2out of the core library's dependency tree@mqttjs/clidepends onmqtt(the core library)mqtt,mqtt_pub,mqtt_subbin commandsPhase 6: Multi-Runtime Support
6.1 Verify Deno compatibility
Uint8Array-native mqtt-packet and no Node.js stream dependency, Deno support should work vianpm:mqttDeno.connect()(optional, community-driven)6.2 Verify Bun compatibility
6.3 React Native validation
react-nativeexports conditionPhase 7: Testing, Benchmarks & Release
7.1 Full test suite pass
7.2 Performance benchmarks
7.3 Bundle size analysis
7.4 Migration guide
7.5 Release
mqtt@6.0.0Breaking Changes Summary (v5 → v6)
WebSocket, stableTextEncoder/TextDecoder, modern streams)wsdependency: Removed — uses nativeWebSocketon Node.js 24+Store.createStream(): May returnAsyncIterableinstead ofReadable(if Phase 4.3 changes the interface)@mqttjs/clipackage (not bundled with core)socksproxy: Published as separate@mqttjs/transport-sockspackageUint8Arrayinstead ofBufferin some contexts (thoughBuffer extends Uint8Arrayso most code works unchanged)Non-Breaking
connect()/connectAsync()API preservedMqttClientevent API preserved (on('message'),on('connect'), etc.)publish(),subscribe(),unsubscribe(),end()APIs preservedExpected Dependency Tree (v6, library consumers)
Total: ~5-8 packages (down from ~75)
Monorepo Structure
Why npm workspaces + Nx (not just npm workspaces)
With 9+ packages, plain npm workspaces lacks:
npm run buildacross packages runs in parallel with no dependency awareness. Nx buildsmqtt-packetfirst, then transports + core in parallel, then CLI — automatically.nx affected, a typo fix intransport-wxtriggers the full test suite across all 9 packages.nx releasehandles this with dependency-aware version bumps.Nx is a devDependency only — it adds zero weight to published packages and zero impact on consumers.
Phasing & Compatibility
This phasing means the community benefits incrementally — dependency reductions in Phase 1-2 land without waiting for the full v6.
Resolved Questions
wsdependency?WebSocket.debugdependency?@mqttjs/clipackage.Continuation of #2002.
CC: @seriousme @mcollina @jacoscaz