Repository: developit/mitt Branch: main Commit: 6b41670516ed Files: 13 Total size: 22.7 KB Directory structure: gitextract__z13ynl8/ ├── .editorconfig ├── .eslintrc ├── .github/ │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── compressed-size.yml │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── src/ │ └── index.ts ├── test/ │ ├── index_test.ts │ └── test-types-compilation.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [{package.json,.*rc,*.yml}] indent_style = space indent_size = 2 insert_final_newline = false [*.md] trim_trailing_whitespace = false indent_style = space indent_size = 2 ================================================ FILE: .eslintrc ================================================ { "ignorePatterns": [ "node_modules", "dist", "index.d.ts" ], "extends": [ "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "developit" ], "parser": "@typescript-eslint/parser", "parserOptions": { "sourceType": "module" }, "env": { "browser": true, "mocha": true, "jest": false, "es6": true }, "globals": { "expect": true }, "rules": { "semi": [ 2, "always" ], "brace-style": [ 2, "1tbs" ], "quotes": [ 2, "single" ], "lines-around-comment": [ 2, { "allowBlockStart": true, "allowObjectStart": true } ], "jest/valid-expect": 0, "@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/explicit-function-return-type": 0, "@typescript-eslint/explicit-module-boundary-types": 0, "@typescript-eslint/no-empty-function": 0, "@typescript-eslint/no-non-null-assertion": 0 } } ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ #### What is the purpose of this pull request? (put an "X" next to an item) - [ ] Documentation update - [ ] Bug fix - [ ] Feature - [ ] Code style update (formatting) - [ ] Refactoring (no functional changes) - [ ] CI related changes - [ ] Other, please explain: #### What changes did you make? (Give an overview) #### Is there anything you'd like reviewers to focus on? #### Does this PR introduce a breaking change? (What changes might other developers need to make in their application due to this PR?) ================================================ FILE: .github/workflows/compressed-size.yml ================================================ name: Compressed Size on: [pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: preactjs/compressed-size-action@v2 with: pattern: "./dist/*.{js,mjs,cjs}" ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: pull_request: branches: - "**" push: branches: - main jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 14 - name: npm install, build, and test run: | npm install npm run build --if-present npm test env: CI: true ================================================ FILE: .gitignore ================================================ /dist /test-reports /node_modules /npm-debug.log /index.d.ts package-lock.json .DS_Store .idea .vscode ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Jason Miller Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

mitt
npm build status gzip size

# Mitt > Tiny 200b functional event emitter / pubsub. - **Microscopic:** weighs less than 200 bytes gzipped - **Useful:** a wildcard `"*"` event type listens to all events - **Familiar:** same names & ideas as [Node's EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) - **Functional:** methods don't rely on `this` - **Great Name:** somehow [mitt](https://npm.im/mitt) wasn't taken Mitt was made for the browser, but works in any JavaScript runtime. It has no dependencies and supports IE9+. ## Table of Contents - [Install](#install) - [Usage](#usage) - [Examples & Demos](#examples--demos) - [API](#api) - [Contribute](#contribute) - [License](#license) ## Install This project uses [node](http://nodejs.org) and [npm](https://npmjs.com). Go check them out if you don't have them locally installed. ```sh $ npm install --save mitt ``` Then with a module bundler like [rollup](http://rollupjs.org/) or [webpack](https://webpack.js.org/), use as you would anything else: ```javascript // using ES6 modules import mitt from 'mitt' // using CommonJS modules var mitt = require('mitt') ``` The [UMD](https://github.com/umdjs/umd) build is also available on [unpkg](https://unpkg.com): ```html ``` You can find the library on `window.mitt`. ## Usage ```js import mitt from 'mitt' const emitter = mitt() // listen to an event emitter.on('foo', e => console.log('foo', e) ) // listen to all events emitter.on('*', (type, e) => console.log(type, e) ) // fire an event emitter.emit('foo', { a: 'b' }) // clearing all events emitter.all.clear() // working with handler references: function onFoo() {} emitter.on('foo', onFoo) // listen emitter.off('foo', onFoo) // unlisten ``` ### Typescript Set `"strict": true` in your tsconfig.json to get improved type inference for `mitt` instance methods. ```ts import mitt from 'mitt'; type Events = { foo: string; bar?: number; }; const emitter = mitt(); // inferred as Emitter emitter.on('foo', (e) => {}); // 'e' has inferred type 'string' emitter.emit('foo', 42); // Error: Argument of type 'number' is not assignable to parameter of type 'string'. (2345) ``` Alternatively, you can use the provided `Emitter` type: ```ts import mitt, { Emitter } from 'mitt'; type Events = { foo: string; bar?: number; }; const emitter: Emitter = mitt(); ``` ## Examples & Demos Preact + Mitt Codepen Demo
preact + mitt preview
* * * ## API #### Table of Contents - [mitt](#mitt) - [all](#all) - [on](#on) - [Parameters](#parameters) - [off](#off) - [Parameters](#parameters-1) - [emit](#emit) - [Parameters](#parameters-2) ### mitt Mitt: Tiny (~200b) functional event emitter / pubsub. Returns **Mitt** ### all A Map of event names to registered handler functions. ### on Register an event handler for the given type. #### Parameters - `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** Type of event to listen for, or `'*'` for all events - `handler` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Function to call in response to given event ### off Remove an event handler for the given type. If `handler` is omitted, all handlers of the given type are removed. #### Parameters - `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** Type of event to unregister `handler` from, or `'*'` - `handler` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)?** Handler function to remove ### emit Invoke all handlers for the given type. If present, `'*'` handlers are invoked after type-matched handlers. Note: Manually firing '\*' handlers is not supported. #### Parameters - `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** The event type to invoke - `evt` **Any?** Any value (object is recommended and powerful), passed to each handler ## Contribute First off, thanks for taking the time to contribute! Now, take a moment to be sure your contributions make sense to everyone else. ### Reporting Issues Found a problem? Want a new feature? First of all see if your issue or idea has [already been reported](../../issues). If don't, just open a [new clear and descriptive issue](../../issues/new). ### Submitting pull requests Pull requests are the greatest contributions, so be sure they are focused in scope, and do avoid unrelated commits. - Fork it! - Clone your fork: `git clone https://github.com//mitt` - Navigate to the newly cloned directory: `cd mitt` - Create a new branch for the new feature: `git checkout -b my-new-feature` - Install the tools necessary for development: `npm install` - Make your changes. - Commit your changes: `git commit -am 'Add some feature'` - Push to the branch: `git push origin my-new-feature` - Submit a pull request with full remarks documenting your changes. ## License [MIT License](https://opensource.org/licenses/MIT) © [Jason Miller](https://jasonformat.com/) ================================================ FILE: package.json ================================================ { "name": "mitt", "version": "3.0.1", "description": "Tiny 200b functional Event Emitter / pubsub.", "module": "dist/mitt.mjs", "main": "dist/mitt.js", "jsnext:main": "dist/mitt.mjs", "umd:main": "dist/mitt.umd.js", "source": "src/index.ts", "typings": "index.d.ts", "exports": { "types": "./index.d.ts", "module": "./dist/mitt.mjs", "import": "./dist/mitt.mjs", "require": "./dist/mitt.js", "default": "./dist/mitt.mjs" }, "scripts": { "test": "npm-run-all --silent typecheck lint mocha test-types", "mocha": "mocha test", "test-types": "tsc test/test-types-compilation.ts --noEmit --strict", "lint": "eslint src test --ext ts --ext js", "typecheck": "tsc --noEmit", "bundle": "microbundle -f es,cjs,umd", "build": "npm-run-all --silent clean -p bundle -s docs", "clean": "rimraf dist", "docs": "documentation readme src/index.ts --section API -q --parse-extension ts", "release": "npm run -s build -s && npm t && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" }, "repository": "developit/mitt", "keywords": [ "events", "eventemitter", "emitter", "pubsub" ], "homepage": "https://github.com/developit/mitt", "authors": [ "Jason Miller " ], "license": "MIT", "files": [ "dist", "index.d.ts" ], "mocha": { "extension": [ "ts" ], "require": [ "ts-node/register", "esm" ], "spec": [ "test/*_test.ts" ] }, "prettier": { "singleQuote": true, "trailingComma": "none" }, "devDependencies": { "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "@types/sinon": "^9.0.4", "@types/sinon-chai": "^3.2.4", "@typescript-eslint/eslint-plugin": "^5.61.0", "@typescript-eslint/parser": "^5.61.0", "chai": "^4.2.0", "documentation": "^14.0.2", "eslint": "^7.32.0", "eslint-config-developit": "^1.2.0", "eslint-plugin-compat": "^4.1.4", "esm": "^3.2.25", "microbundle": "^0.12.3", "mocha": "^8.0.1", "npm-run-all": "^4.1.5", "prettier": "^2.8.8", "rimraf": "^3.0.2", "sinon": "^9.0.2", "sinon-chai": "^3.5.0", "ts-node": "^10.9.1", "typescript": "^4.9.5" } } ================================================ FILE: src/index.ts ================================================ export type EventType = string | symbol; // An event handler can take an optional event argument // and should not return a value export type Handler = (event: T) => void; export type WildcardHandler> = ( type: keyof T, event: T[keyof T] ) => void; // An array of all currently registered event handlers for a type export type EventHandlerList = Array>; export type WildCardEventHandlerList> = Array< WildcardHandler >; // A map of event types and their corresponding event handlers. export type EventHandlerMap> = Map< keyof Events | '*', EventHandlerList | WildCardEventHandlerList >; export interface Emitter> { all: EventHandlerMap; on(type: Key, handler: Handler): void; on(type: '*', handler: WildcardHandler): void; off( type: Key, handler?: Handler ): void; off(type: '*', handler: WildcardHandler): void; emit(type: Key, event: Events[Key]): void; emit( type: undefined extends Events[Key] ? Key : never ): void; } /** * Mitt: Tiny (~200b) functional event emitter / pubsub. * @name mitt * @returns {Mitt} */ export default function mitt>( all?: EventHandlerMap ): Emitter { type GenericEventHandler = | Handler | WildcardHandler; all = all || new Map(); return { /** * A Map of event names to registered handler functions. */ all, /** * Register an event handler for the given type. * @param {string|symbol} type Type of event to listen for, or `'*'` for all events * @param {Function} handler Function to call in response to given event * @memberOf mitt */ on(type: Key, handler: GenericEventHandler) { const handlers: Array | undefined = all!.get(type); if (handlers) { handlers.push(handler); } else { all!.set(type, [handler] as EventHandlerList); } }, /** * Remove an event handler for the given type. * If `handler` is omitted, all handlers of the given type are removed. * @param {string|symbol} type Type of event to unregister `handler` from (`'*'` to remove a wildcard handler) * @param {Function} [handler] Handler function to remove * @memberOf mitt */ off(type: Key, handler?: GenericEventHandler) { const handlers: Array | undefined = all!.get(type); if (handlers) { if (handler) { handlers.splice(handlers.indexOf(handler) >>> 0, 1); } else { all!.set(type, []); } } }, /** * Invoke all handlers for the given type. * If present, `'*'` handlers are invoked after type-matched handlers. * * Note: Manually firing '*' handlers is not supported. * * @param {string|symbol} type The event type to invoke * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler * @memberOf mitt */ emit(type: Key, evt?: Events[Key]) { let handlers = all!.get(type); if (handlers) { (handlers as EventHandlerList) .slice() .map((handler) => { handler(evt!); }); } handlers = all!.get('*'); if (handlers) { (handlers as WildCardEventHandlerList) .slice() .map((handler) => { handler(type, evt!); }); } } }; } ================================================ FILE: test/index_test.ts ================================================ import mitt, { Emitter, EventHandlerMap } from '..'; import chai, { expect } from 'chai'; import { spy } from 'sinon'; import sinonChai from 'sinon-chai'; chai.use(sinonChai); describe('mitt', () => { it('should default export be a function', () => { expect(mitt).to.be.a('function'); }); it('should accept an optional event handler map', () => { expect(() => mitt(new Map())).not.to.throw; const map = new Map(); const a = spy(); const b = spy(); map.set('foo', [a, b]); const events = mitt<{ foo: undefined }>(map); events.emit('foo'); expect(a).to.have.been.calledOnce; expect(b).to.have.been.calledOnce; }); }); describe('mitt#', () => { const eventType = Symbol('eventType'); type Events = { foo: unknown; constructor: unknown; FOO: unknown; bar: unknown; Bar: unknown; 'baz:bat!': unknown; 'baz:baT!': unknown; Foo: unknown; [eventType]: unknown; }; let events: EventHandlerMap, inst: Emitter; beforeEach(() => { events = new Map(); inst = mitt(events); }); describe('properties', () => { it('should expose the event handler map', () => { expect(inst).to.have.property('all').that.is.a('map'); }); }); describe('on()', () => { it('should be a function', () => { expect(inst).to.have.property('on').that.is.a('function'); }); it('should register handler for new type', () => { const foo = () => {}; inst.on('foo', foo); expect(events.get('foo')).to.deep.equal([foo]); }); it('should register handlers for any type strings', () => { const foo = () => {}; inst.on('constructor', foo); expect(events.get('constructor')).to.deep.equal([foo]); }); it('should append handler for existing type', () => { const foo = () => {}; const bar = () => {}; inst.on('foo', foo); inst.on('foo', bar); expect(events.get('foo')).to.deep.equal([foo, bar]); }); it('should NOT normalize case', () => { const foo = () => {}; inst.on('FOO', foo); inst.on('Bar', foo); inst.on('baz:baT!', foo); expect(events.get('FOO')).to.deep.equal([foo]); expect(events.has('foo')).to.equal(false); expect(events.get('Bar')).to.deep.equal([foo]); expect(events.has('bar')).to.equal(false); expect(events.get('baz:baT!')).to.deep.equal([foo]); }); it('can take symbols for event types', () => { const foo = () => {}; inst.on(eventType, foo); expect(events.get(eventType)).to.deep.equal([foo]); }); // Adding the same listener multiple times should register it multiple times. // See https://nodejs.org/api/events.html#events_emitter_on_eventname_listener it('should add duplicate listeners', () => { const foo = () => {}; inst.on('foo', foo); inst.on('foo', foo); expect(events.get('foo')).to.deep.equal([foo, foo]); }); }); describe('off()', () => { it('should be a function', () => { expect(inst).to.have.property('off').that.is.a('function'); }); it('should remove handler for type', () => { const foo = () => {}; inst.on('foo', foo); inst.off('foo', foo); expect(events.get('foo')).to.be.empty; }); it('should NOT normalize case', () => { const foo = () => {}; inst.on('FOO', foo); inst.on('Bar', foo); inst.on('baz:bat!', foo); inst.off('FOO', foo); inst.off('Bar', foo); inst.off('baz:baT!', foo); expect(events.get('FOO')).to.be.empty; expect(events.has('foo')).to.equal(false); expect(events.get('Bar')).to.be.empty; expect(events.has('bar')).to.equal(false); expect(events.get('baz:bat!')).to.have.lengthOf(1); }); it('should remove only the first matching listener', () => { const foo = () => {}; inst.on('foo', foo); inst.on('foo', foo); inst.off('foo', foo); expect(events.get('foo')).to.deep.equal([foo]); inst.off('foo', foo); expect(events.get('foo')).to.deep.equal([]); }); it('off("type") should remove all handlers of the given type', () => { inst.on('foo', () => {}); inst.on('foo', () => {}); inst.on('bar', () => {}); inst.off('foo'); expect(events.get('foo')).to.deep.equal([]); expect(events.get('bar')).to.have.length(1); inst.off('bar'); expect(events.get('bar')).to.deep.equal([]); }); }); describe('emit()', () => { it('should be a function', () => { expect(inst).to.have.property('emit').that.is.a('function'); }); it('should invoke handler for type', () => { const event = { a: 'b' }; inst.on('foo', (one, two?: unknown) => { expect(one).to.deep.equal(event); expect(two).to.be.an('undefined'); }); inst.emit('foo', event); }); it('should NOT ignore case', () => { const onFoo = spy(), onFOO = spy(); events.set('Foo', [onFoo]); events.set('FOO', [onFOO]); inst.emit('Foo', 'Foo arg'); inst.emit('FOO', 'FOO arg'); expect(onFoo).to.have.been.calledOnce.and.calledWith('Foo arg'); expect(onFOO).to.have.been.calledOnce.and.calledWith('FOO arg'); }); it('should invoke * handlers', () => { const star = spy(), ea = { a: 'a' }, eb = { b: 'b' }; events.set('*', [star]); inst.emit('foo', ea); expect(star).to.have.been.calledOnce.and.calledWith('foo', ea); star.resetHistory(); inst.emit('bar', eb); expect(star).to.have.been.calledOnce.and.calledWith('bar', eb); }); }); }); ================================================ FILE: test/test-types-compilation.ts ================================================ /* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unused-vars */ import mitt from '..'; interface SomeEventData { name: string; } const emitter = mitt<{ foo: string; someEvent: SomeEventData; bar?: number; }>(); const barHandler = (x?: number) => {}; const fooHandler = (x: string) => {}; const wildcardHandler = ( _type: 'foo' | 'bar' | 'someEvent', _event: string | SomeEventData | number | undefined ) => {}; /* * Check that 'on' args are inferred correctly */ { // @ts-expect-error emitter.on('foo', barHandler); emitter.on('foo', fooHandler); emitter.on('bar', barHandler); // @ts-expect-error emitter.on('bar', fooHandler); emitter.on('*', wildcardHandler); // fooHandler is ok, because ('foo' | 'bar' | 'someEvent') extends string emitter.on('*', fooHandler); // @ts-expect-error emitter.on('*', barHandler); } /* * Check that 'off' args are inferred correctly */ { // @ts-expect-error emitter.off('foo', barHandler); emitter.off('foo', fooHandler); emitter.off('bar', barHandler); // @ts-expect-error emitter.off('bar', fooHandler); emitter.off('*', wildcardHandler); // fooHandler is ok, because ('foo' | 'bar' | 'someEvent') extends string emitter.off('*', fooHandler); // @ts-expect-error emitter.off('*', barHandler); } /* * Check that 'emit' args are inferred correctly */ { // @ts-expect-error emitter.emit('someEvent', 'NOT VALID'); emitter.emit('someEvent', { name: 'jack' }); // @ts-expect-error emitter.emit('foo'); // @ts-expect-error emitter.emit('foo', 1); emitter.emit('foo', 'string'); emitter.emit('bar'); emitter.emit('bar', 1); // @ts-expect-error emitter.emit('bar', 'string'); } ================================================ FILE: tsconfig.json ================================================ { "compileOnSave": false, "compilerOptions": { "strict": true, "noEmit": true, "declaration": true, "moduleResolution": "node", "skipLibCheck": true, "esModuleInterop": true, }, "include": [ "src/*.ts", "test/*.ts" ] }