eventemitter3 vs mitt vs nanoevents
Lightweight Event Emitters for Frontend Architecture
eventemitter3mittnanoeventsSimilar Packages:

Lightweight Event Emitters for Frontend Architecture

eventemitter3, mitt, and nanoevents are small libraries that implement the publish-subscribe pattern for JavaScript applications. They allow different parts of your code to communicate without direct dependencies, which helps keep components loosely coupled. Unlike the built-in browser EventTarget, these libraries work in Node.js environments and offer simpler APIs for managing listeners and events.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
eventemitter303,52974.4 kB214 months agoMIT
mitt011,87026.4 kB263 years agoMIT
nanoevents01,6275.43 kB02 years agoMIT

Lightweight Event Emitters: eventemitter3 vs mitt vs nanoevents

When building modular frontend applications, you often need a way for components to talk to each other without creating tight dependencies. eventemitter3, mitt, and nanoevents solve this problem using the publish-subscribe pattern. They are all small, but they differ in API style, features, and cleanup mechanisms. Let's look at how they handle real-world tasks.

πŸ—οΈ Setup and Initialization

How you create an emitter instance varies between these libraries. Some use classes, while others use simple functions.

eventemitter3 uses a class constructor, similar to Node.js core modules.

// eventemitter3
import EventEmitter from 'eventemitter3';
const emitter = new EventEmitter();

mitt uses a factory function that returns a plain object.

// mitt
import mitt from 'mitt';
const emitter = mitt();

nanoevents also uses a factory function but focuses on being extremely minimal.

// nanoevents
import nanoevents from 'nanoevents';
const emitter = nanoevents();

πŸ”” Subscribing and Unsubscribing

Managing listener cleanup is crucial to prevent memory leaks. Each package handles removal differently.

eventemitter3 requires you to pass the same function reference to .off() that you used in .on().

// eventemitter3
function handler(data) { console.log(data); }
emitter.on('update', handler);
// Later
emitter.off('update', handler);

mitt works the same way, requiring the original handler for removal.

// mitt
function handler(data) { console.log(data); }
emitter.on('update', handler);
// Later
emitter.off('update', handler);

nanoevents returns an unsubscribe function directly from .on(), which removes the need to store the handler separately.

// nanoevents
const off = emitter.on('update', (data) => { console.log(data); });
// Later
off();

🌐 Wildcard Events

Sometimes you need to listen to every event, such as for global logging or debugging tools.

eventemitter3 does not support wildcard events out of the box. You would need to wrap events or use a plugin.

// eventemitter3
// No built-in wildcard support
// Requires custom wrapper to catch all events
emitter.on('update', handler);

mitt supports the * symbol to catch all emitted events automatically.

// mitt
emitter.on('*', (type, data) => {
  console.log(`Event ${type} fired with`, data);
});

nanoevents does not support wildcard events. It is designed for explicit event names only.

// nanoevents
// No built-in wildcard support
// Must subscribe to each event explicitly
emitter.on('update', handler);

🧭 Context Binding

In object-oriented code, you often need to access the instance this inside an event handler.

eventemitter3 accepts a third argument to bind context directly, avoiding manual .bind() calls.

// eventemitter3
class Component {
  constructor() {
    emitter.on('update', this.handleUpdate, this);
  }
  handleUpdate(data) { console.log(this, data); }
}

mitt does not have built-in context binding. You must use .bind() or arrow functions manually.

// mitt
class Component {
  constructor() {
    emitter.on('update', this.handleUpdate.bind(this));
  }
  handleUpdate(data) { console.log(this, data); }
}

nanoevents also lacks context binding. You must manage scope using arrow functions or bind methods yourself.

// nanoevents
class Component {
  constructor() {
    emitter.on('update', (data) => this.handleUpdate(data));
  }
  handleUpdate(data) { console.log(this, data); }
}

πŸ“Š Summary of Differences

Featureeventemitter3mittnanoevents
StyleClass-basedFactoryFactory
Unsubscribe.off(event, fn).off(event, fn)const off = .on()
Wildcards❌ Noβœ… Yes (*)❌ No
Contextβœ… Built-in❌ Manual❌ Manual
Node.js Readyβœ… Yesβœ… Yesβœ… Yes

πŸ’‘ Final Recommendation

All three libraries are stable and maintained, so there is no risk of choosing a deprecated tool. Your choice depends on your specific architectural needs.

eventemitter3 is the heavy-duty option β€” pick it for complex apps that need Node.js parity and context binding. It feels familiar if you have worked with Node.js streams or core modules.

mitt is the balanced choice β€” ideal for Vue projects or when you need wildcard events for debugging. It offers a clean API without the boilerplate of classes.

nanoevents is the minimal option β€” best for small widgets or libraries where size matters most. The unsubscribe function pattern makes cleanup very clear in functional code.

Final Thought: If you are unsure, mitt offers the best balance of features and simplicity for most frontend work. Choose eventemitter3 for enterprise-scale needs and nanoevents for micro-packages.

How to Choose: eventemitter3 vs mitt vs nanoevents

  • eventemitter3:

    Choose eventemitter3 if you need a robust emitter that works identically in Node.js and the browser. It is the best fit when you require context binding for this in callbacks or need a feature set close to the native Node.js EventEmitter. This package is ideal for large-scale applications where stability and Node.js parity are critical.

  • mitt:

    Choose mitt if you want a simple API with support for wildcard events to catch all messages. It is a great match for Vue.js projects or teams that prefer a clean, factory-based setup without class instantiation. Use this when you need to listen to every event for logging or debugging without extra configuration.

  • nanoevents:

    Choose nanoevents if bundle size is your top priority and you prefer unsubscribe functions over manual removal. It is perfect for micro-libraries or widgets where every byte counts and you want a functional approach to cleanup. This package works well when you do not need wildcards or context binding and want the smallest possible footprint.

README for eventemitter3

EventEmitter3

Version npmCICoverage Status

EventEmitter3 is a high performance EventEmitter. It has been micro-optimized for various of code paths making this, one of, if not the fastest EventEmitter available for Node.js and browsers. The module is API compatible with the EventEmitter that ships by default with Node.js but there are some slight differences:

  • Domain support has been removed.
  • We do not throw an error when you emit an error event and nobody is listening.
  • The newListener and removeListener events have been removed as they are useful only in some uncommon use-cases.
  • The setMaxListeners, getMaxListeners, prependListener and prependOnceListener methods are not available.
  • Support for custom context for events so there is no need to use fn.bind.
  • The removeListener method removes all matching listeners, not only the first.

It's a drop in replacement for existing EventEmitters, but just faster. Free performance, who wouldn't want that? The EventEmitter is written in EcmaScript 3 so it will work in the oldest browsers and node versions that you need to support.

Installation

$ npm install --save eventemitter3

CDN

Recommended CDN:

https://unpkg.com/eventemitter3@latest/dist/eventemitter3.umd.min.js

Usage

After installation the only thing you need to do is require the module:

var EventEmitter = require('eventemitter3');

And you're ready to create your own EventEmitter instances. For the API documentation, please follow the official Node.js documentation:

http://nodejs.org/api/events.html

Contextual emits

We've upgraded the API of the EventEmitter.on, EventEmitter.once and EventEmitter.removeListener to accept an extra argument which is the context or this value that should be set for the emitted events. This means you no longer have the overhead of an event that required fn.bind in order to get a custom this value.

var EE = new EventEmitter()
  , context = { foo: 'bar' };

function emitted() {
  console.log(this === context); // true
}

EE.once('event-name', emitted, context);
EE.on('another-event', emitted, context);
EE.removeListener('another-event', emitted, context);

Tests and benchmarks

To run tests run npm test. To run the benchmarks run npm run benchmark.

Tests and benchmarks are not included in the npm package. If you want to play with them you have to clone the GitHub repository. Note that you will have to run an additional npm i in the benchmarks folder before npm run benchmark.

License

MIT