From 530605f4595ac4aa983f0c3aeb38dfb91ae5e99a Mon Sep 17 00:00:00 2001 From: unional Date: Wed, 23 Nov 2022 01:59:49 -0800 Subject: [PATCH 1/9] feat: add ESM support --- .github/workflows/ci.yml | 1 + index.d.ts | 2 +- index.mjs | 330 ++++++++++++++++++++ package.json | 9 +- test/test.mjs | 638 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 978 insertions(+), 2 deletions(-) create mode 100644 index.mjs create mode 100644 test/test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7a9975..e25af9c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: node-version: ${{ matrix.node }} - run: npm install - run: npm test + - run: npm run test:esm - uses: coverallsapp/github-action@1.1.3 if: matrix.node == 18 with: diff --git a/index.d.ts b/index.d.ts index 118f68b..087778a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -131,4 +131,4 @@ declare namespace EventEmitter { export const EventEmitter: EventEmitterStatic; } -export = EventEmitter; +export default EventEmitter; diff --git a/index.mjs b/index.mjs new file mode 100644 index 0000000..bf1af03 --- /dev/null +++ b/index.mjs @@ -0,0 +1,330 @@ +const has = Object.prototype.hasOwnProperty; +let prefix = '~'; + +/** + * Constructor to create a storage for our `EE` objects. + * An `Events` instance is a plain object whose properties are event names. + * + * @constructor + * @private + */ +function Events() { } + +// +// We try to not inherit from `Object.prototype`. In some engines creating an +// instance in this way is faster than calling `Object.create(null)` directly. +// If `Object.create(null)` is not supported we prefix the event names with a +// character to make sure that the built-in object properties are not +// overridden or used as an attack vector. +// +if (Object.create) { + Events.prototype = Object.create(null); + + // + // This hack is needed because the `__proto__` property is still inherited in + // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. + // + if (!new Events().__proto__) prefix = false; +} + +/** + * Representation of a single event listener. + * + * @param {Function} fn The listener function. + * @param {*} context The context to invoke the listener with. + * @param {Boolean} [once=false] Specify if the listener is a one-time listener. + * @constructor + * @private + */ +function EE(fn, context, once) { + this.fn = fn; + this.context = context; + this.once = once || false; +} + +/** + * Add a listener for a given event. + * + * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} context The context to invoke the listener with. + * @param {Boolean} once Specify if the listener is a one-time listener. + * @returns {EventEmitter} + * @private + */ +function addListener(emitter, event, fn, context, once) { + if (typeof fn !== 'function') { + throw new TypeError('The listener must be a function'); + } + + const listener = new EE(fn, context || emitter, once) + , evt = prefix ? prefix + event : event; + + if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; + else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); + else emitter._events[evt] = [emitter._events[evt], listener]; + + return emitter; +} + +/** + * Clear event by name. + * + * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. + * @param {(String|Symbol)} evt The Event name. + * @private + */ +function clearEvent(emitter, evt) { + if (--emitter._eventsCount === 0) emitter._events = new Events(); + else delete emitter._events[evt]; +} + +/** + * Minimal `EventEmitter` interface that is molded against the Node.js + * `EventEmitter` interface. + * + * @constructor + * @public + */ +function EventEmitter() { + this._events = new Events(); + this._eventsCount = 0; +} + +/** + * Return an array listing the events for which the emitter has registered + * listeners. + * + * @returns {Array} + * @public + */ +EventEmitter.prototype.eventNames = function eventNames() { + const names = [] + let events + , name; + + if (this._eventsCount === 0) return names; + + for (name in (events = this._events)) { + if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); + } + + if (Object.getOwnPropertySymbols) { + return names.concat(Object.getOwnPropertySymbols(events)); + } + + return names; +}; + +/** + * Return the listeners registered for a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Array} The registered listeners. + * @public + */ +EventEmitter.prototype.listeners = function listeners(event) { + const evt = prefix ? prefix + event : event + , handlers = this._events[evt]; + + if (!handlers) return []; + if (handlers.fn) return [handlers.fn]; + + for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { + ee[i] = handlers[i].fn; + } + + return ee; +}; + +/** + * Return the number of listeners listening to a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Number} The number of listeners. + * @public + */ +EventEmitter.prototype.listenerCount = function listenerCount(event) { + const evt = prefix ? prefix + event : event + , listeners = this._events[evt]; + + if (!listeners) return 0; + if (listeners.fn) return 1; + return listeners.length; +}; + +/** + * Calls each of the listeners registered for a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Boolean} `true` if the event had listeners, else `false`. + * @public + */ +EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { + const evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return false; + + const listeners = this._events[evt] + , len = arguments.length + let args + , i; + + if (listeners.fn) { + if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); + + switch (len) { + case 1: return listeners.fn.call(listeners.context), true; + case 2: return listeners.fn.call(listeners.context, a1), true; + case 3: return listeners.fn.call(listeners.context, a1, a2), true; + case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; + case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; + case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; + } + + for (i = 1, args = new Array(len - 1); i < len; i++) { + args[i - 1] = arguments[i]; + } + + listeners.fn.apply(listeners.context, args); + } else { + const length = listeners.length; + let j; + + for (i = 0; i < length; i++) { + if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); + + switch (len) { + case 1: listeners[i].fn.call(listeners[i].context); break; + case 2: listeners[i].fn.call(listeners[i].context, a1); break; + case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; + case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; + default: + if (!args) for (j = 1, args = new Array(len - 1); j < len; j++) { + args[j - 1] = arguments[j]; + } + + listeners[i].fn.apply(listeners[i].context, args); + } + } + } + + return true; +}; + +/** + * Add a listener for a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @public + */ +EventEmitter.prototype.on = function on(event, fn, context) { + return addListener(this, event, fn, context, false); +}; + +/** + * Add a one-time listener for a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @public + */ +EventEmitter.prototype.once = function once(event, fn, context) { + return addListener(this, event, fn, context, true); +}; + +/** + * Remove the listeners of a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn Only remove the listeners that match this function. + * @param {*} context Only remove the listeners that have this context. + * @param {Boolean} once Only remove one-time listeners. + * @returns {EventEmitter} `this`. + * @public + */ +EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { + const evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return this; + if (!fn) { + clearEvent(this, evt); + return this; + } + + const listeners = this._events[evt]; + + if (listeners.fn) { + if ( + listeners.fn === fn && + (!once || listeners.once) && + (!context || listeners.context === context) + ) { + clearEvent(this, evt); + } + } else { + let events = [] + for (let i = 0, length = listeners.length; i < length; i++) { + if ( + listeners[i].fn !== fn || + (once && !listeners[i].once) || + (context && listeners[i].context !== context) + ) { + events.push(listeners[i]); + } + } + + // + // Reset the array, or remove it completely if we have no more listeners. + // + if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; + else clearEvent(this, evt); + } + + return this; +}; + +/** + * Remove all listeners, or those of the specified event. + * + * @param {(String|Symbol)} [event] The event name. + * @returns {EventEmitter} `this`. + * @public + */ +EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { + let evt; + + if (event) { + evt = prefix ? prefix + event : event; + if (this._events[evt]) clearEvent(this, evt); + } else { + this._events = new Events(); + this._eventsCount = 0; + } + + return this; +}; + +// +// Alias methods names because people roll like that. +// +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; +EventEmitter.prototype.addListener = EventEmitter.prototype.on; + +// +// Expose the prefix. +// +EventEmitter.prefixed = prefix; + +// +// Allow `EventEmitter` to be imported as module namespace. +// +EventEmitter.EventEmitter = EventEmitter; + +export default EventEmitter \ No newline at end of file diff --git a/package.json b/package.json index 726b6da..fad6d20 100644 --- a/package.json +++ b/package.json @@ -3,17 +3,23 @@ "version": "4.0.7", "description": "EventEmitter3 focuses on performance while maintaining a Node.js AND browser compatible interface.", "main": "index.js", + "exports": { + "import": "./index.mjs", + "require": "./index.js" + }, "typings": "index.d.ts", "scripts": { - "browserify": "rm -rf umd && mkdir umd && browserify index.js -s EventEmitter3 -o umd/eventemitter3.js", + "browserify": "rimraf -rf umd && mkdir umd && browserify index.js -s EventEmitter3 -o umd/eventemitter3.js", "minify": "uglifyjs umd/eventemitter3.js --source-map -cm -o umd/eventemitter3.min.js", "benchmark": "find benchmarks/run -name '*.js' -exec benchmarks/start.sh {} \\;", "test": "c8 --reporter=lcov --reporter=text mocha test/test.js", + "test:esm": "c8 --reporter=lcov --reporter=text mocha test/test.mjs", "prepublishOnly": "npm run browserify && npm run minify", "test-browser": "node test/browser.js" }, "files": [ "index.js", + "index.mjs", "index.d.ts", "umd" ], @@ -49,6 +55,7 @@ "c8": "^7.3.1", "mocha": "^10.0.0", "pre-commit": "^1.2.0", + "rimraf": "^3.0.2", "sauce-browsers": "^3.0.0", "sauce-test": "^1.3.3", "uglify-js": "^3.9.0" diff --git a/test/test.mjs b/test/test.mjs new file mode 100644 index 0000000..449c3ae --- /dev/null +++ b/test/test.mjs @@ -0,0 +1,638 @@ +import EventEmitter from '../index.mjs' +import assume from 'assume' + +describe('EventEmitter', function tests() { + 'use strict'; + + it('exposes a `prefixed` property', function () { + assume(EventEmitter.prefixed).is.either([false, '~']); + }); + + it('exposes a module namespace object', function () { + assume(EventEmitter.EventEmitter).equals(EventEmitter); + }); + + it('inherits with class syntax', function () { + class Beast extends EventEmitter { } + + var moop = new Beast() + , meap = new Beast(); + + assume(moop).is.instanceOf(Beast); + assume(moop).is.instanceOf(EventEmitter); + + moop.listeners(); + meap.listeners(); + + moop.on('data', function () { + throw new Error('I should not emit'); + }); + + meap.emit('data', 'rawr'); + meap.removeListener('foo'); + meap.removeAllListeners(); + }); + + if ('undefined' !== typeof Symbol) it('works with ES6 symbols', function (next) { + var e = new EventEmitter() + , event = Symbol('cows') + , unknown = Symbol('moo'); + + e.on(event, function foo(arg) { + assume(e.listenerCount(unknown)).equals(0); + assume(e.listeners(unknown)).deep.equals([]); + assume(arg).equals('bar'); + + function bar(onced) { + assume(e.listenerCount(unknown)).equals(0); + assume(e.listeners(unknown)).deep.equals([]); + assume(onced).equals('foo'); + next(); + } + + e.once(unknown, bar); + + assume(e.listenerCount(event)).equals(1); + assume(e.listeners(event)).deep.equals([foo]); + assume(e.listenerCount(unknown)).equals(1); + assume(e.listeners(unknown)).deep.equals([bar]); + + e.removeListener(event); + + assume(e.listenerCount(event)).equals(0); + assume(e.listeners(event)).deep.equals([]); + assume(e.emit(unknown, 'foo')).equals(true); + }); + + assume(e.emit(unknown, 'bar')).equals(false); + assume(e.emit(event, 'bar')).equals(true); + }); + + describe('EventEmitter#emit', function () { + it('should return false when there are not events to emit', function () { + var e = new EventEmitter(); + + assume(e.emit('foo')).equals(false); + assume(e.emit('bar')).equals(false); + }); + + it('emits with context', function (done) { + var context = { bar: 'baz' } + , e = new EventEmitter(); + + e.on('foo', function (bar) { + assume(bar).equals('bar'); + assume(this).equals(context); + + done(); + }, context).emit('foo', 'bar'); + }); + + it('emits with context, multiple arguments (force apply)', function (done) { + var context = { bar: 'baz' } + , e = new EventEmitter(); + + e.on('foo', function (bar) { + assume(bar).equals('bar'); + assume(this).equals(context); + + done(); + }, context).emit('foo', 'bar', 1, 2, 3, 4, 5, 6, 7, 8, 9, 0); + }); + + it('can emit the function with multiple arguments', function () { + var e = new EventEmitter(); + + for (var i = 0; i < 100; i++) { + (function (j) { + for (var i = 0, args = []; i < j; i++) { + args.push(j); + } + + e.once('args', function () { + assume(arguments.length).equals(args.length); + }); + + e.emit.apply(e, ['args'].concat(args)); + })(i); + } + }); + + it('can emit the function with multiple arguments, multiple listeners', function () { + var e = new EventEmitter(); + + for (var i = 0; i < 100; i++) { + (function (j) { + for (var i = 0, args = []; i < j; i++) { + args.push(j); + } + + e.once('args', function () { + assume(arguments.length).equals(args.length); + }); + + e.once('args', function () { + assume(arguments.length).equals(args.length); + }); + + e.once('args', function () { + assume(arguments.length).equals(args.length); + }); + + e.once('args', function () { + assume(arguments.length).equals(args.length); + }); + + e.emit.apply(e, ['args'].concat(args)); + })(i); + } + }); + + it('emits with context, multiple listeners (force loop)', function () { + var e = new EventEmitter(); + + e.on('foo', function (bar) { + assume(this).eqls({ foo: 'bar' }); + assume(bar).equals('bar'); + }, { foo: 'bar' }); + + e.on('foo', function (bar) { + assume(this).eqls({ bar: 'baz' }); + assume(bar).equals('bar'); + }, { bar: 'baz' }); + + e.emit('foo', 'bar'); + }); + + it('emits with different contexts', function () { + var e = new EventEmitter() + , pattern = ''; + + function writer() { + pattern += this; + } + + e.on('write', writer, 'foo'); + e.on('write', writer, 'baz'); + e.once('write', writer, 'bar'); + e.once('write', writer, 'banana'); + + e.emit('write'); + assume(pattern).equals('foobazbarbanana'); + }); + + it('should return true when there are events to emit', function () { + var e = new EventEmitter() + , called = 0; + + e.on('foo', function () { + called++; + }); + + assume(e.emit('foo')).equals(true); + assume(e.emit('foob')).equals(false); + assume(called).equals(1); + }); + + it('receives the emitted events', function (done) { + var e = new EventEmitter(); + + e.on('data', function (a, b, c, d, undef) { + assume(a).equals('foo'); + assume(b).equals(e); + assume(c).is.instanceOf(Date); + assume(undef).equals(undefined); + assume(arguments.length).equals(3); + + done(); + }); + + e.emit('data', 'foo', e, new Date()); + }); + + it('emits to all event listeners', function () { + var e = new EventEmitter() + , pattern = []; + + e.on('foo', function () { + pattern.push('foo1'); + }); + + e.on('foo', function () { + pattern.push('foo2'); + }); + + e.emit('foo'); + + assume(pattern.join(';')).equals('foo1;foo2'); + }); + + (function each(keys) { + var key = keys.shift(); + + if (!key) return; + + it('can store event which is a known property: ' + key, function (next) { + var e = new EventEmitter(); + + e.on(key, function (k) { + assume(k).equals(key); + next(); + }).emit(key, key); + }); + + each(keys); + })([ + 'hasOwnProperty', + 'constructor', + '__proto__', + 'toString', + 'toValue', + 'unwatch', + 'watch' + ]); + }); + + describe('EventEmitter#listeners', function () { + it('returns an empty array if no listeners are specified', function () { + var e = new EventEmitter(); + + assume(e.listeners('foo')).is.a('array'); + assume(e.listeners('foo').length).equals(0); + }); + + it('returns an array of function', function () { + var e = new EventEmitter(); + + function foo() { } + + e.on('foo', foo); + assume(e.listeners('foo')).is.a('array'); + assume(e.listeners('foo').length).equals(1); + assume(e.listeners('foo')).deep.equals([foo]); + }); + + it('is not vulnerable to modifications', function () { + var e = new EventEmitter(); + + function foo() { } + + e.on('foo', foo); + + assume(e.listeners('foo')).deep.equals([foo]); + + e.listeners('foo').length = 0; + assume(e.listeners('foo')).deep.equals([foo]); + }); + }); + + describe('EventEmitter#listenerCount', function () { + it('returns the number of listeners for a given event', function () { + var e = new EventEmitter(); + + assume(e.listenerCount()).equals(0); + assume(e.listenerCount('foo')).equals(0); + + e.on('foo', function () { }); + assume(e.listenerCount('foo')).equals(1); + e.on('foo', function () { }); + assume(e.listenerCount('foo')).equals(2); + }); + }); + + describe('EventEmitter#on', function () { + it('throws an error if the listener is not a function', function () { + var e = new EventEmitter(); + + try { + e.on('foo', 'bar'); + } catch (ex) { + assume(ex).is.instanceOf(TypeError); + assume(ex.message).equals('The listener must be a function'); + return; + } + + throw new Error('oops'); + }); + }); + + describe('EventEmitter#once', function () { + it('only emits it once', function () { + var e = new EventEmitter() + , calls = 0; + + e.once('foo', function () { + calls++; + }); + + e.emit('foo'); + e.emit('foo'); + e.emit('foo'); + e.emit('foo'); + e.emit('foo'); + + assume(e.listeners('foo').length).equals(0); + assume(calls).equals(1); + }); + + it('only emits once if emits are nested inside the listener', function () { + var e = new EventEmitter() + , calls = 0; + + e.once('foo', function () { + calls++; + e.emit('foo'); + }); + + e.emit('foo'); + assume(e.listeners('foo').length).equals(0); + assume(calls).equals(1); + }); + + it('only emits once for multiple events', function () { + var e = new EventEmitter() + , multi = 0 + , foo = 0 + , bar = 0; + + e.once('foo', function () { + foo++; + }); + + e.once('foo', function () { + bar++; + }); + + e.on('foo', function () { + multi++; + }); + + e.emit('foo'); + e.emit('foo'); + e.emit('foo'); + e.emit('foo'); + e.emit('foo'); + + assume(e.listeners('foo').length).equals(1); + assume(multi).equals(5); + assume(foo).equals(1); + assume(bar).equals(1); + }); + + it('only emits once with context', function (done) { + var context = { foo: 'bar' } + , e = new EventEmitter(); + + e.once('foo', function (bar) { + assume(this).equals(context); + assume(bar).equals('bar'); + + done(); + }, context).emit('foo', 'bar'); + }); + }); + + describe('EventEmitter#removeListener', function () { + it('removes all listeners when the listener is not specified', function () { + var e = new EventEmitter(); + + e.on('foo', function () { }); + e.on('foo', function () { }); + + assume(e.removeListener('foo')).equals(e); + assume(e.listeners('foo')).eql([]); + }); + + it('removes only the listeners matching the specified listener', function () { + var e = new EventEmitter(); + + function foo() { } + function bar() { } + function baz() { } + + e.on('foo', foo); + e.on('bar', bar); + e.on('bar', baz); + + assume(e.removeListener('foo', bar)).equals(e); + assume(e.listeners('bar')).eql([bar, baz]); + assume(e.listeners('foo')).eql([foo]); + assume(e._eventsCount).equals(2); + + assume(e.removeListener('foo', foo)).equals(e); + assume(e.listeners('bar')).eql([bar, baz]); + assume(e.listeners('foo')).eql([]); + assume(e._eventsCount).equals(1); + + assume(e.removeListener('bar', bar)).equals(e); + assume(e.listeners('bar')).eql([baz]); + assume(e._eventsCount).equals(1); + + assume(e.removeListener('bar', baz)).equals(e); + assume(e.listeners('bar')).eql([]); + assume(e._eventsCount).equals(0); + + e.on('foo', foo); + e.on('foo', foo); + e.on('bar', bar); + + assume(e.removeListener('foo', foo)).equals(e); + assume(e.listeners('bar')).eql([bar]); + assume(e.listeners('foo')).eql([]); + assume(e._eventsCount).equals(1); + }); + + it('removes only the once listeners when using the once flag', function () { + var e = new EventEmitter(); + + function foo() { } + + e.on('foo', foo); + + assume(e.removeListener('foo', function () { }, undefined, true)).equals(e); + assume(e.listeners('foo')).eql([foo]); + assume(e._eventsCount).equals(1); + + assume(e.removeListener('foo', foo, undefined, true)).equals(e); + assume(e.listeners('foo')).eql([foo]); + assume(e._eventsCount).equals(1); + + assume(e.removeListener('foo', foo)).equals(e); + assume(e.listeners('foo')).eql([]); + assume(e._eventsCount).equals(0); + + e.once('foo', foo); + e.on('foo', foo); + + assume(e.removeListener('foo', function () { }, undefined, true)).equals(e); + assume(e.listeners('foo')).eql([foo, foo]); + assume(e._eventsCount).equals(1); + + assume(e.removeListener('foo', foo, undefined, true)).equals(e); + assume(e.listeners('foo')).eql([foo]); + assume(e._eventsCount).equals(1); + + e.once('foo', foo); + + assume(e.removeListener('foo', foo)).equals(e); + assume(e.listeners('foo')).eql([]); + assume(e._eventsCount).equals(0); + }); + + it('removes only the listeners matching the correct context', function () { + var context = { foo: 'bar' } + , e = new EventEmitter(); + + function foo() { } + function bar() { } + + e.on('foo', foo, context); + + assume(e.removeListener('foo', function () { }, context)).equals(e); + assume(e.listeners('foo')).eql([foo]); + assume(e._eventsCount).equals(1); + + assume(e.removeListener('foo', foo, { baz: 'quux' })).equals(e); + assume(e.listeners('foo')).eql([foo]); + assume(e._eventsCount).equals(1); + + assume(e.removeListener('foo', foo, context)).equals(e); + assume(e.listeners('foo')).eql([]); + assume(e._eventsCount).equals(0); + + e.on('foo', foo, context); + e.on('foo', bar); + + assume(e.removeListener('foo', foo, { baz: 'quux' })).equals(e); + assume(e.listeners('foo')).eql([foo, bar]); + assume(e._eventsCount).equals(1); + + assume(e.removeListener('foo', foo, context)).equals(e); + assume(e.listeners('foo')).eql([bar]); + assume(e._eventsCount).equals(1); + + e.on('foo', bar, context); + + assume(e.removeListener('foo', bar)).equals(e); + assume(e.listeners('foo')).eql([]); + assume(e._eventsCount).equals(0); + }); + }); + + describe('EventEmitter#removeAllListeners', function () { + it('removes all events for the specified events', function () { + var e = new EventEmitter(); + + e.on('foo', function () { throw new Error('oops'); }); + e.on('foo', function () { throw new Error('oops'); }); + e.on('bar', function () { throw new Error('oops'); }); + e.on('aaa', function () { throw new Error('oops'); }); + + assume(e.removeAllListeners('foo')).equals(e); + assume(e.listeners('foo').length).equals(0); + assume(e.listeners('bar').length).equals(1); + assume(e.listeners('aaa').length).equals(1); + assume(e._eventsCount).equals(2); + + assume(e.removeAllListeners('bar')).equals(e); + assume(e._eventsCount).equals(1); + assume(e.removeAllListeners('aaa')).equals(e); + assume(e._eventsCount).equals(0); + + assume(e.emit('foo')).equals(false); + assume(e.emit('bar')).equals(false); + assume(e.emit('aaa')).equals(false); + }); + + it('just nukes the fuck out of everything', function () { + var e = new EventEmitter(); + + e.on('foo', function () { throw new Error('oops'); }); + e.on('foo', function () { throw new Error('oops'); }); + e.on('bar', function () { throw new Error('oops'); }); + e.on('aaa', function () { throw new Error('oops'); }); + + assume(e.removeAllListeners()).equals(e); + assume(e.listeners('foo').length).equals(0); + assume(e.listeners('bar').length).equals(0); + assume(e.listeners('aaa').length).equals(0); + assume(e._eventsCount).equals(0); + + assume(e.emit('foo')).equals(false); + assume(e.emit('bar')).equals(false); + assume(e.emit('aaa')).equals(false); + }); + }); + + describe('EventEmitter#eventNames', function () { + it('returns an empty array when there are no events', function () { + var e = new EventEmitter(); + + assume(e.eventNames()).eql([]); + + e.on('foo', function () { }); + e.removeAllListeners('foo'); + + assume(e.eventNames()).eql([]); + }); + + it('returns an array listing the events that have listeners', function () { + var e = new EventEmitter() + , original; + + function bar() { } + + if (Object.getOwnPropertySymbols) { + // + // Monkey patch `Object.getOwnPropertySymbols()` to increase coverage + // on Node.js > 0.10. + // + original = Object.getOwnPropertySymbols; + Object.getOwnPropertySymbols = undefined; + } + + e.on('foo', function () { }); + e.on('bar', bar); + + try { + assume(e.eventNames()).eql(['foo', 'bar']); + e.removeListener('bar', bar); + assume(e.eventNames()).eql(['foo']); + } catch (ex) { + throw ex; + } finally { + if (original) Object.getOwnPropertySymbols = original; + } + }); + + it('does not return inherited property identifiers', function () { + var e = new EventEmitter(); + + function Collection() { } + Collection.prototype.foo = function () { + return 'foo'; + }; + + e._events = new Collection(); + + assume(e._events.foo()).equal('foo'); + assume(e.eventNames()).eql([]); + }); + + if ('undefined' !== typeof Symbol) it('includes ES6 symbols', function () { + var e = new EventEmitter() + , s = Symbol('s'); + + function foo() { } + + e.on('foo', foo); + e.on(s, function () { }); + + assume(e.eventNames()).eql(['foo', s]); + + e.removeListener('foo', foo); + + assume(e.eventNames()).eql([s]); + }); + }); +}); From aea0ef99f23e26a8fd0a327315c92a5d7b0f0e6d Mon Sep 17 00:00:00 2001 From: unional Date: Wed, 23 Nov 2022 09:48:46 -0800 Subject: [PATCH 2/9] refactor: try re-export CSJ->ESM --- index.mjs | 329 +----------------------------------------------------- 1 file changed, 1 insertion(+), 328 deletions(-) diff --git a/index.mjs b/index.mjs index bf1af03..1131521 100644 --- a/index.mjs +++ b/index.mjs @@ -1,330 +1,3 @@ -const has = Object.prototype.hasOwnProperty; -let prefix = '~'; - -/** - * Constructor to create a storage for our `EE` objects. - * An `Events` instance is a plain object whose properties are event names. - * - * @constructor - * @private - */ -function Events() { } - -// -// We try to not inherit from `Object.prototype`. In some engines creating an -// instance in this way is faster than calling `Object.create(null)` directly. -// If `Object.create(null)` is not supported we prefix the event names with a -// character to make sure that the built-in object properties are not -// overridden or used as an attack vector. -// -if (Object.create) { - Events.prototype = Object.create(null); - - // - // This hack is needed because the `__proto__` property is still inherited in - // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. - // - if (!new Events().__proto__) prefix = false; -} - -/** - * Representation of a single event listener. - * - * @param {Function} fn The listener function. - * @param {*} context The context to invoke the listener with. - * @param {Boolean} [once=false] Specify if the listener is a one-time listener. - * @constructor - * @private - */ -function EE(fn, context, once) { - this.fn = fn; - this.context = context; - this.once = once || false; -} - -/** - * Add a listener for a given event. - * - * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. - * @param {(String|Symbol)} event The event name. - * @param {Function} fn The listener function. - * @param {*} context The context to invoke the listener with. - * @param {Boolean} once Specify if the listener is a one-time listener. - * @returns {EventEmitter} - * @private - */ -function addListener(emitter, event, fn, context, once) { - if (typeof fn !== 'function') { - throw new TypeError('The listener must be a function'); - } - - const listener = new EE(fn, context || emitter, once) - , evt = prefix ? prefix + event : event; - - if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; - else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); - else emitter._events[evt] = [emitter._events[evt], listener]; - - return emitter; -} - -/** - * Clear event by name. - * - * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. - * @param {(String|Symbol)} evt The Event name. - * @private - */ -function clearEvent(emitter, evt) { - if (--emitter._eventsCount === 0) emitter._events = new Events(); - else delete emitter._events[evt]; -} - -/** - * Minimal `EventEmitter` interface that is molded against the Node.js - * `EventEmitter` interface. - * - * @constructor - * @public - */ -function EventEmitter() { - this._events = new Events(); - this._eventsCount = 0; -} - -/** - * Return an array listing the events for which the emitter has registered - * listeners. - * - * @returns {Array} - * @public - */ -EventEmitter.prototype.eventNames = function eventNames() { - const names = [] - let events - , name; - - if (this._eventsCount === 0) return names; - - for (name in (events = this._events)) { - if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); - } - - if (Object.getOwnPropertySymbols) { - return names.concat(Object.getOwnPropertySymbols(events)); - } - - return names; -}; - -/** - * Return the listeners registered for a given event. - * - * @param {(String|Symbol)} event The event name. - * @returns {Array} The registered listeners. - * @public - */ -EventEmitter.prototype.listeners = function listeners(event) { - const evt = prefix ? prefix + event : event - , handlers = this._events[evt]; - - if (!handlers) return []; - if (handlers.fn) return [handlers.fn]; - - for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { - ee[i] = handlers[i].fn; - } - - return ee; -}; - -/** - * Return the number of listeners listening to a given event. - * - * @param {(String|Symbol)} event The event name. - * @returns {Number} The number of listeners. - * @public - */ -EventEmitter.prototype.listenerCount = function listenerCount(event) { - const evt = prefix ? prefix + event : event - , listeners = this._events[evt]; - - if (!listeners) return 0; - if (listeners.fn) return 1; - return listeners.length; -}; - -/** - * Calls each of the listeners registered for a given event. - * - * @param {(String|Symbol)} event The event name. - * @returns {Boolean} `true` if the event had listeners, else `false`. - * @public - */ -EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { - const evt = prefix ? prefix + event : event; - - if (!this._events[evt]) return false; - - const listeners = this._events[evt] - , len = arguments.length - let args - , i; - - if (listeners.fn) { - if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); - - switch (len) { - case 1: return listeners.fn.call(listeners.context), true; - case 2: return listeners.fn.call(listeners.context, a1), true; - case 3: return listeners.fn.call(listeners.context, a1, a2), true; - case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; - case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; - case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; - } - - for (i = 1, args = new Array(len - 1); i < len; i++) { - args[i - 1] = arguments[i]; - } - - listeners.fn.apply(listeners.context, args); - } else { - const length = listeners.length; - let j; - - for (i = 0; i < length; i++) { - if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); - - switch (len) { - case 1: listeners[i].fn.call(listeners[i].context); break; - case 2: listeners[i].fn.call(listeners[i].context, a1); break; - case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; - case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; - default: - if (!args) for (j = 1, args = new Array(len - 1); j < len; j++) { - args[j - 1] = arguments[j]; - } - - listeners[i].fn.apply(listeners[i].context, args); - } - } - } - - return true; -}; - -/** - * Add a listener for a given event. - * - * @param {(String|Symbol)} event The event name. - * @param {Function} fn The listener function. - * @param {*} [context=this] The context to invoke the listener with. - * @returns {EventEmitter} `this`. - * @public - */ -EventEmitter.prototype.on = function on(event, fn, context) { - return addListener(this, event, fn, context, false); -}; - -/** - * Add a one-time listener for a given event. - * - * @param {(String|Symbol)} event The event name. - * @param {Function} fn The listener function. - * @param {*} [context=this] The context to invoke the listener with. - * @returns {EventEmitter} `this`. - * @public - */ -EventEmitter.prototype.once = function once(event, fn, context) { - return addListener(this, event, fn, context, true); -}; - -/** - * Remove the listeners of a given event. - * - * @param {(String|Symbol)} event The event name. - * @param {Function} fn Only remove the listeners that match this function. - * @param {*} context Only remove the listeners that have this context. - * @param {Boolean} once Only remove one-time listeners. - * @returns {EventEmitter} `this`. - * @public - */ -EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { - const evt = prefix ? prefix + event : event; - - if (!this._events[evt]) return this; - if (!fn) { - clearEvent(this, evt); - return this; - } - - const listeners = this._events[evt]; - - if (listeners.fn) { - if ( - listeners.fn === fn && - (!once || listeners.once) && - (!context || listeners.context === context) - ) { - clearEvent(this, evt); - } - } else { - let events = [] - for (let i = 0, length = listeners.length; i < length; i++) { - if ( - listeners[i].fn !== fn || - (once && !listeners[i].once) || - (context && listeners[i].context !== context) - ) { - events.push(listeners[i]); - } - } - - // - // Reset the array, or remove it completely if we have no more listeners. - // - if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; - else clearEvent(this, evt); - } - - return this; -}; - -/** - * Remove all listeners, or those of the specified event. - * - * @param {(String|Symbol)} [event] The event name. - * @returns {EventEmitter} `this`. - * @public - */ -EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { - let evt; - - if (event) { - evt = prefix ? prefix + event : event; - if (this._events[evt]) clearEvent(this, evt); - } else { - this._events = new Events(); - this._eventsCount = 0; - } - - return this; -}; - -// -// Alias methods names because people roll like that. -// -EventEmitter.prototype.off = EventEmitter.prototype.removeListener; -EventEmitter.prototype.addListener = EventEmitter.prototype.on; - -// -// Expose the prefix. -// -EventEmitter.prefixed = prefix; - -// -// Allow `EventEmitter` to be imported as module namespace. -// -EventEmitter.EventEmitter = EventEmitter; +import EventEmitter from './index.js' export default EventEmitter \ No newline at end of file From f3f6c3b7137763f177d3bb13a8a445d9b4011ba2 Mon Sep 17 00:00:00 2001 From: unional Date: Wed, 23 Nov 2022 10:04:35 -0800 Subject: [PATCH 3/9] fix: export EventEmitter also as named export --- index.d.ts | 1 + index.mjs | 1 + 2 files changed, 2 insertions(+) diff --git a/index.d.ts b/index.d.ts index 087778a..fab37fd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -131,4 +131,5 @@ declare namespace EventEmitter { export const EventEmitter: EventEmitterStatic; } +export { EventEmitter } export default EventEmitter; diff --git a/index.mjs b/index.mjs index 1131521..acb7920 100644 --- a/index.mjs +++ b/index.mjs @@ -1,3 +1,4 @@ import EventEmitter from './index.js' +export { EventEmitter } export default EventEmitter \ No newline at end of file From 8ed87f71433dacea711ece5979f7b2580687c549 Mon Sep 17 00:00:00 2001 From: unional Date: Wed, 23 Nov 2022 10:57:04 -0800 Subject: [PATCH 4/9] chore: remove tests --- .github/workflows/ci.yml | 1 - package.json | 1 - test/test.mjs | 638 --------------------------------------- 3 files changed, 640 deletions(-) delete mode 100644 test/test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e25af9c..d7a9975 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,6 @@ jobs: node-version: ${{ matrix.node }} - run: npm install - run: npm test - - run: npm run test:esm - uses: coverallsapp/github-action@1.1.3 if: matrix.node == 18 with: diff --git a/package.json b/package.json index fad6d20..1aa8441 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "minify": "uglifyjs umd/eventemitter3.js --source-map -cm -o umd/eventemitter3.min.js", "benchmark": "find benchmarks/run -name '*.js' -exec benchmarks/start.sh {} \\;", "test": "c8 --reporter=lcov --reporter=text mocha test/test.js", - "test:esm": "c8 --reporter=lcov --reporter=text mocha test/test.mjs", "prepublishOnly": "npm run browserify && npm run minify", "test-browser": "node test/browser.js" }, diff --git a/test/test.mjs b/test/test.mjs deleted file mode 100644 index 449c3ae..0000000 --- a/test/test.mjs +++ /dev/null @@ -1,638 +0,0 @@ -import EventEmitter from '../index.mjs' -import assume from 'assume' - -describe('EventEmitter', function tests() { - 'use strict'; - - it('exposes a `prefixed` property', function () { - assume(EventEmitter.prefixed).is.either([false, '~']); - }); - - it('exposes a module namespace object', function () { - assume(EventEmitter.EventEmitter).equals(EventEmitter); - }); - - it('inherits with class syntax', function () { - class Beast extends EventEmitter { } - - var moop = new Beast() - , meap = new Beast(); - - assume(moop).is.instanceOf(Beast); - assume(moop).is.instanceOf(EventEmitter); - - moop.listeners(); - meap.listeners(); - - moop.on('data', function () { - throw new Error('I should not emit'); - }); - - meap.emit('data', 'rawr'); - meap.removeListener('foo'); - meap.removeAllListeners(); - }); - - if ('undefined' !== typeof Symbol) it('works with ES6 symbols', function (next) { - var e = new EventEmitter() - , event = Symbol('cows') - , unknown = Symbol('moo'); - - e.on(event, function foo(arg) { - assume(e.listenerCount(unknown)).equals(0); - assume(e.listeners(unknown)).deep.equals([]); - assume(arg).equals('bar'); - - function bar(onced) { - assume(e.listenerCount(unknown)).equals(0); - assume(e.listeners(unknown)).deep.equals([]); - assume(onced).equals('foo'); - next(); - } - - e.once(unknown, bar); - - assume(e.listenerCount(event)).equals(1); - assume(e.listeners(event)).deep.equals([foo]); - assume(e.listenerCount(unknown)).equals(1); - assume(e.listeners(unknown)).deep.equals([bar]); - - e.removeListener(event); - - assume(e.listenerCount(event)).equals(0); - assume(e.listeners(event)).deep.equals([]); - assume(e.emit(unknown, 'foo')).equals(true); - }); - - assume(e.emit(unknown, 'bar')).equals(false); - assume(e.emit(event, 'bar')).equals(true); - }); - - describe('EventEmitter#emit', function () { - it('should return false when there are not events to emit', function () { - var e = new EventEmitter(); - - assume(e.emit('foo')).equals(false); - assume(e.emit('bar')).equals(false); - }); - - it('emits with context', function (done) { - var context = { bar: 'baz' } - , e = new EventEmitter(); - - e.on('foo', function (bar) { - assume(bar).equals('bar'); - assume(this).equals(context); - - done(); - }, context).emit('foo', 'bar'); - }); - - it('emits with context, multiple arguments (force apply)', function (done) { - var context = { bar: 'baz' } - , e = new EventEmitter(); - - e.on('foo', function (bar) { - assume(bar).equals('bar'); - assume(this).equals(context); - - done(); - }, context).emit('foo', 'bar', 1, 2, 3, 4, 5, 6, 7, 8, 9, 0); - }); - - it('can emit the function with multiple arguments', function () { - var e = new EventEmitter(); - - for (var i = 0; i < 100; i++) { - (function (j) { - for (var i = 0, args = []; i < j; i++) { - args.push(j); - } - - e.once('args', function () { - assume(arguments.length).equals(args.length); - }); - - e.emit.apply(e, ['args'].concat(args)); - })(i); - } - }); - - it('can emit the function with multiple arguments, multiple listeners', function () { - var e = new EventEmitter(); - - for (var i = 0; i < 100; i++) { - (function (j) { - for (var i = 0, args = []; i < j; i++) { - args.push(j); - } - - e.once('args', function () { - assume(arguments.length).equals(args.length); - }); - - e.once('args', function () { - assume(arguments.length).equals(args.length); - }); - - e.once('args', function () { - assume(arguments.length).equals(args.length); - }); - - e.once('args', function () { - assume(arguments.length).equals(args.length); - }); - - e.emit.apply(e, ['args'].concat(args)); - })(i); - } - }); - - it('emits with context, multiple listeners (force loop)', function () { - var e = new EventEmitter(); - - e.on('foo', function (bar) { - assume(this).eqls({ foo: 'bar' }); - assume(bar).equals('bar'); - }, { foo: 'bar' }); - - e.on('foo', function (bar) { - assume(this).eqls({ bar: 'baz' }); - assume(bar).equals('bar'); - }, { bar: 'baz' }); - - e.emit('foo', 'bar'); - }); - - it('emits with different contexts', function () { - var e = new EventEmitter() - , pattern = ''; - - function writer() { - pattern += this; - } - - e.on('write', writer, 'foo'); - e.on('write', writer, 'baz'); - e.once('write', writer, 'bar'); - e.once('write', writer, 'banana'); - - e.emit('write'); - assume(pattern).equals('foobazbarbanana'); - }); - - it('should return true when there are events to emit', function () { - var e = new EventEmitter() - , called = 0; - - e.on('foo', function () { - called++; - }); - - assume(e.emit('foo')).equals(true); - assume(e.emit('foob')).equals(false); - assume(called).equals(1); - }); - - it('receives the emitted events', function (done) { - var e = new EventEmitter(); - - e.on('data', function (a, b, c, d, undef) { - assume(a).equals('foo'); - assume(b).equals(e); - assume(c).is.instanceOf(Date); - assume(undef).equals(undefined); - assume(arguments.length).equals(3); - - done(); - }); - - e.emit('data', 'foo', e, new Date()); - }); - - it('emits to all event listeners', function () { - var e = new EventEmitter() - , pattern = []; - - e.on('foo', function () { - pattern.push('foo1'); - }); - - e.on('foo', function () { - pattern.push('foo2'); - }); - - e.emit('foo'); - - assume(pattern.join(';')).equals('foo1;foo2'); - }); - - (function each(keys) { - var key = keys.shift(); - - if (!key) return; - - it('can store event which is a known property: ' + key, function (next) { - var e = new EventEmitter(); - - e.on(key, function (k) { - assume(k).equals(key); - next(); - }).emit(key, key); - }); - - each(keys); - })([ - 'hasOwnProperty', - 'constructor', - '__proto__', - 'toString', - 'toValue', - 'unwatch', - 'watch' - ]); - }); - - describe('EventEmitter#listeners', function () { - it('returns an empty array if no listeners are specified', function () { - var e = new EventEmitter(); - - assume(e.listeners('foo')).is.a('array'); - assume(e.listeners('foo').length).equals(0); - }); - - it('returns an array of function', function () { - var e = new EventEmitter(); - - function foo() { } - - e.on('foo', foo); - assume(e.listeners('foo')).is.a('array'); - assume(e.listeners('foo').length).equals(1); - assume(e.listeners('foo')).deep.equals([foo]); - }); - - it('is not vulnerable to modifications', function () { - var e = new EventEmitter(); - - function foo() { } - - e.on('foo', foo); - - assume(e.listeners('foo')).deep.equals([foo]); - - e.listeners('foo').length = 0; - assume(e.listeners('foo')).deep.equals([foo]); - }); - }); - - describe('EventEmitter#listenerCount', function () { - it('returns the number of listeners for a given event', function () { - var e = new EventEmitter(); - - assume(e.listenerCount()).equals(0); - assume(e.listenerCount('foo')).equals(0); - - e.on('foo', function () { }); - assume(e.listenerCount('foo')).equals(1); - e.on('foo', function () { }); - assume(e.listenerCount('foo')).equals(2); - }); - }); - - describe('EventEmitter#on', function () { - it('throws an error if the listener is not a function', function () { - var e = new EventEmitter(); - - try { - e.on('foo', 'bar'); - } catch (ex) { - assume(ex).is.instanceOf(TypeError); - assume(ex.message).equals('The listener must be a function'); - return; - } - - throw new Error('oops'); - }); - }); - - describe('EventEmitter#once', function () { - it('only emits it once', function () { - var e = new EventEmitter() - , calls = 0; - - e.once('foo', function () { - calls++; - }); - - e.emit('foo'); - e.emit('foo'); - e.emit('foo'); - e.emit('foo'); - e.emit('foo'); - - assume(e.listeners('foo').length).equals(0); - assume(calls).equals(1); - }); - - it('only emits once if emits are nested inside the listener', function () { - var e = new EventEmitter() - , calls = 0; - - e.once('foo', function () { - calls++; - e.emit('foo'); - }); - - e.emit('foo'); - assume(e.listeners('foo').length).equals(0); - assume(calls).equals(1); - }); - - it('only emits once for multiple events', function () { - var e = new EventEmitter() - , multi = 0 - , foo = 0 - , bar = 0; - - e.once('foo', function () { - foo++; - }); - - e.once('foo', function () { - bar++; - }); - - e.on('foo', function () { - multi++; - }); - - e.emit('foo'); - e.emit('foo'); - e.emit('foo'); - e.emit('foo'); - e.emit('foo'); - - assume(e.listeners('foo').length).equals(1); - assume(multi).equals(5); - assume(foo).equals(1); - assume(bar).equals(1); - }); - - it('only emits once with context', function (done) { - var context = { foo: 'bar' } - , e = new EventEmitter(); - - e.once('foo', function (bar) { - assume(this).equals(context); - assume(bar).equals('bar'); - - done(); - }, context).emit('foo', 'bar'); - }); - }); - - describe('EventEmitter#removeListener', function () { - it('removes all listeners when the listener is not specified', function () { - var e = new EventEmitter(); - - e.on('foo', function () { }); - e.on('foo', function () { }); - - assume(e.removeListener('foo')).equals(e); - assume(e.listeners('foo')).eql([]); - }); - - it('removes only the listeners matching the specified listener', function () { - var e = new EventEmitter(); - - function foo() { } - function bar() { } - function baz() { } - - e.on('foo', foo); - e.on('bar', bar); - e.on('bar', baz); - - assume(e.removeListener('foo', bar)).equals(e); - assume(e.listeners('bar')).eql([bar, baz]); - assume(e.listeners('foo')).eql([foo]); - assume(e._eventsCount).equals(2); - - assume(e.removeListener('foo', foo)).equals(e); - assume(e.listeners('bar')).eql([bar, baz]); - assume(e.listeners('foo')).eql([]); - assume(e._eventsCount).equals(1); - - assume(e.removeListener('bar', bar)).equals(e); - assume(e.listeners('bar')).eql([baz]); - assume(e._eventsCount).equals(1); - - assume(e.removeListener('bar', baz)).equals(e); - assume(e.listeners('bar')).eql([]); - assume(e._eventsCount).equals(0); - - e.on('foo', foo); - e.on('foo', foo); - e.on('bar', bar); - - assume(e.removeListener('foo', foo)).equals(e); - assume(e.listeners('bar')).eql([bar]); - assume(e.listeners('foo')).eql([]); - assume(e._eventsCount).equals(1); - }); - - it('removes only the once listeners when using the once flag', function () { - var e = new EventEmitter(); - - function foo() { } - - e.on('foo', foo); - - assume(e.removeListener('foo', function () { }, undefined, true)).equals(e); - assume(e.listeners('foo')).eql([foo]); - assume(e._eventsCount).equals(1); - - assume(e.removeListener('foo', foo, undefined, true)).equals(e); - assume(e.listeners('foo')).eql([foo]); - assume(e._eventsCount).equals(1); - - assume(e.removeListener('foo', foo)).equals(e); - assume(e.listeners('foo')).eql([]); - assume(e._eventsCount).equals(0); - - e.once('foo', foo); - e.on('foo', foo); - - assume(e.removeListener('foo', function () { }, undefined, true)).equals(e); - assume(e.listeners('foo')).eql([foo, foo]); - assume(e._eventsCount).equals(1); - - assume(e.removeListener('foo', foo, undefined, true)).equals(e); - assume(e.listeners('foo')).eql([foo]); - assume(e._eventsCount).equals(1); - - e.once('foo', foo); - - assume(e.removeListener('foo', foo)).equals(e); - assume(e.listeners('foo')).eql([]); - assume(e._eventsCount).equals(0); - }); - - it('removes only the listeners matching the correct context', function () { - var context = { foo: 'bar' } - , e = new EventEmitter(); - - function foo() { } - function bar() { } - - e.on('foo', foo, context); - - assume(e.removeListener('foo', function () { }, context)).equals(e); - assume(e.listeners('foo')).eql([foo]); - assume(e._eventsCount).equals(1); - - assume(e.removeListener('foo', foo, { baz: 'quux' })).equals(e); - assume(e.listeners('foo')).eql([foo]); - assume(e._eventsCount).equals(1); - - assume(e.removeListener('foo', foo, context)).equals(e); - assume(e.listeners('foo')).eql([]); - assume(e._eventsCount).equals(0); - - e.on('foo', foo, context); - e.on('foo', bar); - - assume(e.removeListener('foo', foo, { baz: 'quux' })).equals(e); - assume(e.listeners('foo')).eql([foo, bar]); - assume(e._eventsCount).equals(1); - - assume(e.removeListener('foo', foo, context)).equals(e); - assume(e.listeners('foo')).eql([bar]); - assume(e._eventsCount).equals(1); - - e.on('foo', bar, context); - - assume(e.removeListener('foo', bar)).equals(e); - assume(e.listeners('foo')).eql([]); - assume(e._eventsCount).equals(0); - }); - }); - - describe('EventEmitter#removeAllListeners', function () { - it('removes all events for the specified events', function () { - var e = new EventEmitter(); - - e.on('foo', function () { throw new Error('oops'); }); - e.on('foo', function () { throw new Error('oops'); }); - e.on('bar', function () { throw new Error('oops'); }); - e.on('aaa', function () { throw new Error('oops'); }); - - assume(e.removeAllListeners('foo')).equals(e); - assume(e.listeners('foo').length).equals(0); - assume(e.listeners('bar').length).equals(1); - assume(e.listeners('aaa').length).equals(1); - assume(e._eventsCount).equals(2); - - assume(e.removeAllListeners('bar')).equals(e); - assume(e._eventsCount).equals(1); - assume(e.removeAllListeners('aaa')).equals(e); - assume(e._eventsCount).equals(0); - - assume(e.emit('foo')).equals(false); - assume(e.emit('bar')).equals(false); - assume(e.emit('aaa')).equals(false); - }); - - it('just nukes the fuck out of everything', function () { - var e = new EventEmitter(); - - e.on('foo', function () { throw new Error('oops'); }); - e.on('foo', function () { throw new Error('oops'); }); - e.on('bar', function () { throw new Error('oops'); }); - e.on('aaa', function () { throw new Error('oops'); }); - - assume(e.removeAllListeners()).equals(e); - assume(e.listeners('foo').length).equals(0); - assume(e.listeners('bar').length).equals(0); - assume(e.listeners('aaa').length).equals(0); - assume(e._eventsCount).equals(0); - - assume(e.emit('foo')).equals(false); - assume(e.emit('bar')).equals(false); - assume(e.emit('aaa')).equals(false); - }); - }); - - describe('EventEmitter#eventNames', function () { - it('returns an empty array when there are no events', function () { - var e = new EventEmitter(); - - assume(e.eventNames()).eql([]); - - e.on('foo', function () { }); - e.removeAllListeners('foo'); - - assume(e.eventNames()).eql([]); - }); - - it('returns an array listing the events that have listeners', function () { - var e = new EventEmitter() - , original; - - function bar() { } - - if (Object.getOwnPropertySymbols) { - // - // Monkey patch `Object.getOwnPropertySymbols()` to increase coverage - // on Node.js > 0.10. - // - original = Object.getOwnPropertySymbols; - Object.getOwnPropertySymbols = undefined; - } - - e.on('foo', function () { }); - e.on('bar', bar); - - try { - assume(e.eventNames()).eql(['foo', 'bar']); - e.removeListener('bar', bar); - assume(e.eventNames()).eql(['foo']); - } catch (ex) { - throw ex; - } finally { - if (original) Object.getOwnPropertySymbols = original; - } - }); - - it('does not return inherited property identifiers', function () { - var e = new EventEmitter(); - - function Collection() { } - Collection.prototype.foo = function () { - return 'foo'; - }; - - e._events = new Collection(); - - assume(e._events.foo()).equal('foo'); - assume(e.eventNames()).eql([]); - }); - - if ('undefined' !== typeof Symbol) it('includes ES6 symbols', function () { - var e = new EventEmitter() - , s = Symbol('s'); - - function foo() { } - - e.on('foo', foo); - e.on(s, function () { }); - - assume(e.eventNames()).eql(['foo', s]); - - e.removeListener('foo', foo); - - assume(e.eventNames()).eql([s]); - }); - }); -}); From b0fec2763e7a36fdf59e5e685a0e3137c80299ff Mon Sep 17 00:00:00 2001 From: unional Date: Wed, 23 Nov 2022 12:13:04 -0800 Subject: [PATCH 5/9] fix: update exports mapping --- index.mjs | 2 +- package.json | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/index.mjs b/index.mjs index acb7920..89bb405 100644 --- a/index.mjs +++ b/index.mjs @@ -1,4 +1,4 @@ import EventEmitter from './index.js' export { EventEmitter } -export default EventEmitter \ No newline at end of file +export default EventEmitter diff --git a/package.json b/package.json index 1aa8441..9ab8151 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,21 @@ "name": "eventemitter3", "version": "4.0.7", "description": "EventEmitter3 focuses on performance while maintaining a Node.js AND browser compatible interface.", - "main": "index.js", "exports": { - "import": "./index.mjs", - "require": "./index.js" + ".": { + "import": "./index.mjs", + "require": "./index.js", + "types": "./index.d.ts" + }, + "./umd/eventemitter3.js": { + "import": "./umd/eventemitter3.js", + "require": "./umd/eventemitter3.js", + "types": "./index.d.ts" + }, + "./package.json": "./package.json" }, - "typings": "index.d.ts", + "main": "index.js", + "types": "index.d.ts", "scripts": { "browserify": "rimraf -rf umd && mkdir umd && browserify index.js -s EventEmitter3 -o umd/eventemitter3.js", "minify": "uglifyjs umd/eventemitter3.js --source-map -cm -o umd/eventemitter3.min.js", From 4030a86898d5fa39be73e9a1d2738b0d4f982b0b Mon Sep 17 00:00:00 2001 From: unional Date: Wed, 23 Nov 2022 13:15:23 -0800 Subject: [PATCH 6/9] feat: add esm bundle bundle using rollup --- .github/workflows/ci.yml | 1 + .gitignore | 2 +- package.json | 20 ++++++++------------ rollup.config.mjs | 28 ++++++++++++++++++++++++++++ test/test.mjs | 9 +++++++++ 5 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 rollup.config.mjs create mode 100644 test/test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7a9975..978f940 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: node-version: ${{ matrix.node }} - run: npm install - run: npm test + - run: npm test-esm - uses: coverallsapp/github-action@1.1.3 if: matrix.node == 18 with: diff --git a/.gitignore b/.gitignore index 66275fe..eec701f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ node_modules/ coverage/ -umd/ +dist/ .tern-port diff --git a/package.json b/package.json index 9ab8151..0b88c12 100644 --- a/package.json +++ b/package.json @@ -8,28 +8,23 @@ "require": "./index.js", "types": "./index.d.ts" }, - "./umd/eventemitter3.js": { - "import": "./umd/eventemitter3.js", - "require": "./umd/eventemitter3.js", - "types": "./index.d.ts" - }, "./package.json": "./package.json" }, "main": "index.js", "types": "index.d.ts", "scripts": { - "browserify": "rimraf -rf umd && mkdir umd && browserify index.js -s EventEmitter3 -o umd/eventemitter3.js", - "minify": "uglifyjs umd/eventemitter3.js --source-map -cm -o umd/eventemitter3.min.js", + "rollup": "rimraf dist umd && rollup -c", "benchmark": "find benchmarks/run -name '*.js' -exec benchmarks/start.sh {} \\;", "test": "c8 --reporter=lcov --reporter=text mocha test/test.js", - "prepublishOnly": "npm run browserify && npm run minify", + "test-esm": "c8 --reporter=lcov --reporter=text mocha test/test.mjs", + "prepublishOnly": "npm run rollup", "test-browser": "node test/browser.js" }, "files": [ "index.js", "index.mjs", "index.d.ts", - "umd" + "dist" ], "repository": { "type": "git", @@ -58,14 +53,15 @@ "url": "https://github.com/primus/eventemitter3/issues" }, "devDependencies": { + "@rollup/plugin-commonjs": "^23.0.2", + "@rollup/plugin-terser": "^0.1.0", "assume": "^2.2.0", - "browserify": "^17.0.0", "c8": "^7.3.1", "mocha": "^10.0.0", "pre-commit": "^1.2.0", "rimraf": "^3.0.2", + "rollup": "^3.4.0", "sauce-browsers": "^3.0.0", - "sauce-test": "^1.3.3", - "uglify-js": "^3.9.0" + "sauce-test": "^1.3.3" } } diff --git a/rollup.config.mjs b/rollup.config.mjs new file mode 100644 index 0000000..e95d5e0 --- /dev/null +++ b/rollup.config.mjs @@ -0,0 +1,28 @@ +import commonjs from '@rollup/plugin-commonjs'; +import terser from '@rollup/plugin-terser'; + +export default [{ + input: './index.mjs', + output: { + file: 'dist/eventemitter3.esm.js', + format: 'es' + }, + plugins: [commonjs()] +}, { + input: './index.js', + output: { + format: 'umd', + name: 'EventEmitter3', + file: 'dist/eventemitter3.umd.js' + }, + plugins: [commonjs()] +}, { + input: './index.js', + output: { + compact: true, + format: 'umd', + name: 'EventEmitter3', + file: 'dist/eventemitter3.umd.min.js' + }, + plugins: [commonjs(), terser()] +}]; diff --git a/test/test.mjs b/test/test.mjs new file mode 100644 index 0000000..7b50c82 --- /dev/null +++ b/test/test.mjs @@ -0,0 +1,9 @@ +import EventEmitterDefault, { EventEmitter } from '../index.mjs'; + +it('exports `EventEmitter` as default export', () => { + new EventEmitterDefault(); +}) + +it('exports `EventEmitter` as a named export', () => { + new EventEmitter(); +}) From 8d1d94fd136e66c91a304cbfa2f65a92d36793fd Mon Sep 17 00:00:00 2001 From: Homa Wong Date: Fri, 25 Nov 2022 12:20:31 -0800 Subject: [PATCH 7/9] Update .github/workflows/ci.yml Co-authored-by: Luigi Pinca --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 978f940..c584c56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: node-version: ${{ matrix.node }} - run: npm install - run: npm test - - run: npm test-esm + - run: npm run test-esm - uses: coverallsapp/github-action@1.1.3 if: matrix.node == 18 with: From e81d8f7a5808f8aef6b69b82594221a878c90a57 Mon Sep 17 00:00:00 2001 From: unional Date: Fri, 25 Nov 2022 12:32:49 -0800 Subject: [PATCH 8/9] chore: add esm.min --- rollup.config.mjs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index e95d5e0..64ce462 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -8,21 +8,29 @@ export default [{ format: 'es' }, plugins: [commonjs()] +}, { + input: './index.mjs', + output: { + compact: true, + file: 'dist/eventemitter3.esm.min.js', + format: 'es' + }, + plugins: [commonjs(), terser()] }, { input: './index.js', output: { + file: 'dist/eventemitter3.umd.js', format: 'umd', - name: 'EventEmitter3', - file: 'dist/eventemitter3.umd.js' + name: 'EventEmitter3' }, plugins: [commonjs()] }, { input: './index.js', output: { compact: true, + file: 'dist/eventemitter3.umd.min.js', format: 'umd', - name: 'EventEmitter3', - file: 'dist/eventemitter3.umd.min.js' + name: 'EventEmitter3' }, plugins: [commonjs(), terser()] }]; From 41e2f86ff8a83c7047ba84e95400dd9c6eb547f6 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Mon, 28 Nov 2022 14:06:30 +0100 Subject: [PATCH 9/9] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0b88c12..6549fdc 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "rollup": "rimraf dist umd && rollup -c", "benchmark": "find benchmarks/run -name '*.js' -exec benchmarks/start.sh {} \\;", "test": "c8 --reporter=lcov --reporter=text mocha test/test.js", - "test-esm": "c8 --reporter=lcov --reporter=text mocha test/test.mjs", + "test-esm": "mocha test/test.mjs", "prepublishOnly": "npm run rollup", "test-browser": "node test/browser.js" },