Skip to content

Commit

Permalink
Replace Node EventEmitter with standard EventTarget
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalduez committed Sep 2, 2024
1 parent ecacffb commit 6496100
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 47 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Remove usage of Node core lib util module ([#1](https://github.com/Gandi/counterpart/pull/1)).
- Remove usage of `except` helper package ([#1](https://github.com/Gandi/counterpart/pull/2)).
- **BREAKING** Replace usage of Node `EventEmitter` with native `EventTarget` ([#1](https://github.com/Gandi/counterpart/pull/3)).
This make the package able to run server of client side without requiring polyfills.
This change the signature of event listener callbacks.
They now receive a single `Event` object as argument, instead of unlimited
parameters. So for instance:
```
// Before
instance.onLocaleChange((locale, previous) => {})
```
```
// After
instance.onLocaleChange((event) => {
// event.detail.locale
// event.detail.previous
})
```


[unreleased]: https://github.com/gandi/counterpart/compare/0.18.6...HEAD
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
BIN = ./node_modules/.bin

test: lint
test:
@$(BIN)/mocha -t 5000 -b -R spec spec.js

lint: node_modules/
Expand Down
105 changes: 72 additions & 33 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

var extend = require('extend');
var sprintf = require("sprintf-js").sprintf;
var events = require('events');

var strftime = require('./strftime');

Expand Down Expand Up @@ -46,29 +45,69 @@ function getEntry(translations, keys) {
}, translations);
}

function Counterpart() {
events.EventEmitter.apply(this);

this._registry = {
locale: 'en',
interpolate: true,
fallbackLocales: [],
scope: null,
translations: {},
interpolations: {},
normalizedKeys: {},
separator: '.',
keepTrailingDot: false,
keyTransformer: function(key) { return key; },
generateMissingEntry: function(key) { return 'missing translation: ' + key; }
};

this.registerTranslations('en', require('./locales/en'));
this.setMaxListeners(0);
}
// EventTarget does not (yet) have a native way to retrieve attached listeners.
// See https://github.com/whatwg/dom/issues/412
let addEventListener = EventTarget.prototype.addEventListener;
let removeEventListener = EventTarget.prototype.removeEventListener;

EventTarget.prototype.addEventListener = function(type, listener, options) {
addEventListener.call(this, type, listener, options);
(this._events[type] ||= []).push(listener);
return this;
};

EventTarget.prototype.removeEventListener = function(type, listener, options) {
removeEventListener.call(this, type, listener, options);

let list = this._events[type];

if (!list) return this;

let position = -1;

for (let i = list.length - 1; i >= 0; i--) {
if (list[i] === listener) {
position = i;
break;
}
}

Counterpart.prototype = events.EventEmitter.prototype;
Counterpart.prototype.constructor = events.EventEmitter;
if (position < 0) return this;

if (position === 0) {
list.shift();
}
else {
list.splice(list, position);
}

return this;
};

class Counterpart extends EventTarget {

constructor() {
super();

this._events = {};

this._registry = {
locale: 'en',
interpolate: true,
fallbackLocales: [],
scope: null,
translations: {},
interpolations: {},
normalizedKeys: {},
separator: '.',
keepTrailingDot: false,
keyTransformer: function(key) { return key; },
generateMissingEntry: function(key) { return 'missing translation: ' + key; }
};

this.registerTranslations('en', require('./locales/en'));
}
}

Counterpart.prototype.getLocale = function() {
return this._registry.locale;
Expand All @@ -79,7 +118,7 @@ Counterpart.prototype.setLocale = function(value) {

if (previous != value) {
this._registry.locale = value;
this.emit('localechange', value, previous);
this.dispatchEvent(new CustomEvent('localechange', { detail: { locale: value, previous }}));
}

return previous;
Expand Down Expand Up @@ -158,32 +197,32 @@ Counterpart.prototype.registerInterpolations = function(data) {

Counterpart.prototype.onLocaleChange =
Counterpart.prototype.addLocaleChangeListener = function(callback) {
this.addListener('localechange', callback);
this.addEventListener('localechange', callback);
};

Counterpart.prototype.offLocaleChange =
Counterpart.prototype.removeLocaleChangeListener = function(callback) {
this.removeListener('localechange', callback);
this.removeEventListener('localechange', callback);
};

Counterpart.prototype.onTranslationNotFound =
Counterpart.prototype.addTranslationNotFoundListener = function(callback) {
this.addListener('translationnotfound', callback);
this.addEventListener('translationnotfound', callback);
};

Counterpart.prototype.offTranslationNotFound =
Counterpart.prototype.removeTranslationNotFoundListener = function(callback) {
this.removeListener('translationnotfound', callback);
this.removeEventListener('translationnotfound', callback);
};

Counterpart.prototype.onError =
Counterpart.prototype.addErrorListener = function(callback) {
this.addListener('error', callback);
this.addEventListener('error', callback);
};

Counterpart.prototype.offError =
Counterpart.prototype.removeErrorListener = function(callback) {
this.removeListener('error', callback);
this.removeEventListener('error', callback);
};

Counterpart.prototype.translate = function(key, options) {
Expand Down Expand Up @@ -216,7 +255,7 @@ Counterpart.prototype.translate = function(key, options) {
var entry = getEntry(this._registry.translations, keys);

if (entry === null) {
this.emit('translationnotfound', locale, key, options.fallback, scope);
this.dispatchEvent(new CustomEvent('translationnotfound', { detail: { locale, key, fallback: options.fallback, scope }}));

if (options.fallback) {
entry = this._fallback(locale, scope, key, options.fallback, options);
Expand Down Expand Up @@ -354,8 +393,8 @@ Counterpart.prototype._interpolate = function(entry, values) {
try {
return sprintf(entry, extend({}, this._registry.interpolations, values));
} catch (err) {
if (this.listenerCount('error') > 0) {
this.emit('error', err, entry, values);
if (this._events?.error?.length > 0) {
this.dispatchEvent(new CustomEvent('error', { detail: { error: err, entry, values }}));
} else {
throw err;
}
Expand Down
27 changes: 14 additions & 13 deletions spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -607,9 +607,9 @@ describe('translate', function() {
var oldLocale = instance.getLocale();
var newLocale = oldLocale + 'x';

var handler = function(locale, previousLocale) {
assert.equal(locale, newLocale);
assert.equal(previousLocale, oldLocale);
var handler = function(evt) {
assert.equal(evt.detail.locale, newLocale);
assert.equal(evt.detail.previous, oldLocale);
done();
};

Expand All @@ -619,7 +619,8 @@ describe('translate', function() {
});
});

describe('when called more than 10 times', function() {
// EventTarget does not have a native `setMaxListeners`.
describe.skip('when called more than 10 times', function() {
it('does not let Node issue a warning about a possible memory leak', function() {
var oldConsoleError = console.error;

Expand Down Expand Up @@ -694,11 +695,11 @@ describe('translate', function() {

describe('when called', function() {
it('exposes the current locale, key, fallback and scope as arguments', function(done) {
var handler = function(locale, key, fallback, scope) {
assert.equal('yy', locale);
assert.equal('foo', key);
assert.equal('bar', fallback);
assert.equal('zz', scope);
var handler = function(evt) {
assert.equal('yy', evt.detail.locale);
assert.equal('foo', evt.detail.key);
assert.equal('bar', evt.detail.fallback);
assert.equal('zz', evt.detail.scope);
done();
};

Expand Down Expand Up @@ -763,10 +764,10 @@ describe('translate', function() {

describe('when called', function() {
it('exposes the error, entry and values as arguments', function(done) {
var handler = function(error, entry, values) {
assert.notEqual(undefined, error);
assert.equal('Hello, %(name)s!', entry);
assert.deepEqual({}, values);
var handler = function(evt) {
assert.notEqual(undefined, evt.detail.error);
assert.equal('Hello, %(name)s!', evt.detail.entry);
assert.deepEqual({}, evt.detail.values);
done();
};

Expand Down

0 comments on commit 6496100

Please sign in to comment.