Adding upstream version 1.2.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
eac40c2ddc
commit
e864a6175d
14 changed files with 584 additions and 0 deletions
7
.babelrc
Normal file
7
.babelrc
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"modules": "umd",
|
||||
"loose": "all",
|
||||
"compact": true,
|
||||
"comments": false,
|
||||
"stage": 0
|
||||
}
|
50
.eslintrc
Normal file
50
.eslintrc
Normal file
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"parser": "babel-eslint",
|
||||
"extends": "eslint:recommended",
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"ecmaFeatures": {
|
||||
"modules": true,
|
||||
"jsx": true
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": [1, { "args": "after-used" }],
|
||||
"no-cond-assign": 1,
|
||||
"semi": 2,
|
||||
"camelcase": 0,
|
||||
"comma-style": 2,
|
||||
"comma-dangle": [2, "never"],
|
||||
"indent": [2, "tab", {"SwitchCase": 1}],
|
||||
"no-mixed-spaces-and-tabs": [2, "smart-tabs"],
|
||||
"no-trailing-spaces": [2, { "skipBlankLines": true }],
|
||||
"max-nested-callbacks": [2, 3],
|
||||
"no-eval": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-new-func": 2,
|
||||
"guard-for-in": 2,
|
||||
"eqeqeq": 2,
|
||||
"no-else-return": 2,
|
||||
"no-redeclare": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"radix": 2,
|
||||
"strict": [2, "never"],
|
||||
"no-shadow": 0,
|
||||
"callback-return": [1, ["callback", "cb", "next", "done"]],
|
||||
"no-delete-var": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"handle-callback-err": 0,
|
||||
"no-lonely-if": 2,
|
||||
"space-return-throw-case": 2,
|
||||
"constructor-super": 2,
|
||||
"no-this-before-super": 2,
|
||||
"no-dupe-class-members": 2,
|
||||
"no-const-assign": 2,
|
||||
"prefer-spread": 2,
|
||||
"no-useless-concat": 2,
|
||||
"no-var": 2,
|
||||
"object-shorthand": 2,
|
||||
"prefer-arrow-callback": 2
|
||||
}
|
||||
}
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
npm-debug.log
|
||||
node_modules
|
||||
dist
|
||||
.DS_Store
|
1
.travis.yml
Normal file
1
.travis.yml
Normal file
|
@ -0,0 +1 @@
|
|||
language: node_js
|
20
LICENSE
Normal file
20
LICENSE
Normal file
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 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.
|
120
README.md
Normal file
120
README.md
Normal file
|
@ -0,0 +1,120 @@
|
|||
# decko [](https://npmjs.com/package/decko) [](https://travis-ci.org/developit/decko)
|
||||
|
||||
A concise implementation of the three most useful [decorators](https://github.com/wycats/javascript-decorators):
|
||||
|
||||
- `@bind`: make the value of `this` constant within a method
|
||||
- `@debounce`: throttle calls to a method
|
||||
- `@memoize`: cache return values based on arguments
|
||||
|
||||
Decorators help simplify code by replacing the noise of common patterns with declarative annotations.
|
||||
Conversely, decorators can also be overused and create obscurity.
|
||||
Decko establishes 3 standard decorators that are immediately recognizable, so you can avoid creating decorators in your own codebase.
|
||||
|
||||
> 💡 **Tip:** decko is particularly well-suited to [**Preact Classful Components**](https://github.com/developit/preact).
|
||||
>
|
||||
> 💫 **Note:**
|
||||
> - For Babel 6+, be sure to install [babel-plugin-transform-decorators-legacy](https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy).
|
||||
> - For Typescript, be sure to enable `{"experimentalDecorators": true}` in your tsconfig.json.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Available on [npm](https://npmjs.com/package/decko):
|
||||
|
||||
```sh
|
||||
npm i -S decko
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Each decorator method is available as a named import.
|
||||
|
||||
```js
|
||||
import { bind, memoize, debounce } from 'decko';
|
||||
```
|
||||
|
||||
|
||||
### `@bind`
|
||||
|
||||
```js
|
||||
class Example {
|
||||
@bind
|
||||
foo() {
|
||||
// the value of `this` is always the object from which foo() was referenced.
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
let e = new Example();
|
||||
assert.equal(e.foo.call(null), e);
|
||||
```
|
||||
|
||||
|
||||
|
||||
### `@memoize`
|
||||
|
||||
> Cache values returned from the decorated function.
|
||||
> Uses the first argument as a cache key.
|
||||
> _Cache keys are always converted to strings._
|
||||
>
|
||||
> ##### Options:
|
||||
>
|
||||
> `caseSensitive: false` - _Makes cache keys case-insensitive_
|
||||
>
|
||||
> `cache: {}` - _Presupply cache storage, for seeding or sharing entries_
|
||||
|
||||
```js
|
||||
class Example {
|
||||
@memoize
|
||||
expensive(key) {
|
||||
let start = Date.now();
|
||||
while (Date.now()-start < 500) key++;
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
let e = new Example();
|
||||
|
||||
// this takes 500ms
|
||||
let one = e.expensive(1);
|
||||
|
||||
// this takes 0ms
|
||||
let two = e.expensive(1);
|
||||
|
||||
// this takes 500ms
|
||||
let three = e.expensive(2);
|
||||
```
|
||||
|
||||
|
||||
|
||||
### `@debounce`
|
||||
|
||||
> Throttle calls to the decorated function. To debounce means "call this at most once per N ms".
|
||||
> All outward function calls get collated into a single inward call, and only the latest (most recent) arguments as passed on to the debounced function.
|
||||
>
|
||||
> ##### Options:
|
||||
>
|
||||
> `delay: 0` - _The number of milliseconds to buffer calls for._
|
||||
|
||||
```js
|
||||
class Example {
|
||||
@debounce
|
||||
foo() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
let e = new Example();
|
||||
|
||||
// this will only call foo() once:
|
||||
for (let i=1000; i--) e.foo();
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT
|
33
package.json
Normal file
33
package.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "decko",
|
||||
"version": "1.2.0",
|
||||
"main": "dist/decko.js",
|
||||
"types": "dist/decko.d.ts",
|
||||
"description": "A collection of the most useful property decorators.",
|
||||
"scripts": {
|
||||
"build": "mkdir -p dist && babel -f src/decko.js -s -o $npm_package_main < src/decko.js && npm run build:ts",
|
||||
"build:ts": "cp src/decko.d.ts dist/",
|
||||
"test": "npm run test:ts && eslint {src,tests}/**.js && mocha --compilers js:babel/register tests/**/*.js",
|
||||
"test:ts": "tsc -p ./",
|
||||
"style:ts": "tsfmt -r",
|
||||
"prepublish": "npm run build",
|
||||
"release": "npm run build && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"dist"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/developit/decko.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel": "^5.8.21",
|
||||
"babel-eslint": "^4.1.6",
|
||||
"chai": "^3.2.0",
|
||||
"eslint": "^1.10.3",
|
||||
"mocha": "^2.3.0",
|
||||
"typescript": "2.1.6",
|
||||
"typescript-formatter": "4.1.1"
|
||||
}
|
||||
}
|
30
src/decko.d.ts
vendored
Normal file
30
src/decko.d.ts
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
export function bind<T>(
|
||||
target: Object,
|
||||
propertyKey: string | symbol,
|
||||
descriptor?: TypedPropertyDescriptor<T>
|
||||
): TypedPropertyDescriptor<T> | void;
|
||||
export function bind(): MethodDecorator;
|
||||
|
||||
/**
|
||||
* @param caseSensitive Makes cache keys case-insensitive
|
||||
* @param cache Presupply cache storage, for seeding or sharing entries
|
||||
*/
|
||||
|
||||
export function memoize<T>(
|
||||
target: Object,
|
||||
propertyKey: string | symbol,
|
||||
descriptor?: TypedPropertyDescriptor<T>
|
||||
): TypedPropertyDescriptor<T> | void;
|
||||
export function memoize(caseSensitive?: boolean, cache?: Object): MethodDecorator;
|
||||
/**
|
||||
* @param delay number
|
||||
*/
|
||||
export function debounce<T>(
|
||||
target: Object,
|
||||
propertyKey: string | symbol,
|
||||
descriptor?: TypedPropertyDescriptor<T>
|
||||
): TypedPropertyDescriptor<T> | void;
|
||||
export function debounce(delay?: number): MethodDecorator;
|
98
src/decko.js
Normal file
98
src/decko.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
|
||||
const EMPTY = {};
|
||||
const HOP = Object.prototype.hasOwnProperty;
|
||||
|
||||
let fns = {
|
||||
/** let cachedFn = memoize(originalFn); */
|
||||
memoize(fn, opt=EMPTY) {
|
||||
let cache = opt.cache || {};
|
||||
return function(...a) {
|
||||
let k = String(a[0]);
|
||||
if (opt.caseSensitive===false) k = k.toLowerCase();
|
||||
return HOP.call(cache,k) ? cache[k] : (cache[k] = fn.apply(this, a));
|
||||
};
|
||||
},
|
||||
|
||||
/** let throttled = debounce(10, console.log); */
|
||||
debounce(fn, opts) {
|
||||
if (typeof opts==='function') { let p = fn; fn = opts; opts = p; }
|
||||
let delay = opts && opts.delay || opts || 0,
|
||||
args, context, timer;
|
||||
return function(...a) {
|
||||
args = a;
|
||||
context = this;
|
||||
if (!timer) timer = setTimeout( () => {
|
||||
fn.apply(context, args);
|
||||
args = context = timer = null;
|
||||
}, delay);
|
||||
};
|
||||
},
|
||||
|
||||
bind(target, key, { value: fn }) {
|
||||
return {
|
||||
configurable: true,
|
||||
get() {
|
||||
let value = fn.bind(this);
|
||||
Object.defineProperty(this, key, {
|
||||
value,
|
||||
configurable: true,
|
||||
writable: true
|
||||
});
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let memoize = multiMethod(fns.memoize),
|
||||
debounce = multiMethod(fns.debounce),
|
||||
bind = multiMethod((f,c)=>f.bind(c), ()=>fns.bind);
|
||||
|
||||
export { memoize, debounce, bind };
|
||||
export default { memoize, debounce, bind };
|
||||
|
||||
|
||||
/** Creates a function that supports the following calling styles:
|
||||
* d() - returns an unconfigured decorator
|
||||
* d(opts) - returns a configured decorator
|
||||
* d(fn, opts) - returns a decorated proxy to `fn`
|
||||
* d(target, key, desc) - the decorator itself
|
||||
*
|
||||
* @Example:
|
||||
* // simple identity deco:
|
||||
* let d = multiMethod( fn => fn );
|
||||
*
|
||||
* class Foo {
|
||||
* @d
|
||||
* bar() { }
|
||||
*
|
||||
* @d()
|
||||
* baz() { }
|
||||
*
|
||||
* @d({ opts })
|
||||
* bat() { }
|
||||
*
|
||||
* bap = d(() => {})
|
||||
* }
|
||||
*/
|
||||
function multiMethod(inner, deco) {
|
||||
deco = deco || inner.decorate || decorator(inner);
|
||||
let d = deco();
|
||||
return (...args) => {
|
||||
let l = args.length;
|
||||
return (l<2 ? deco : (l>2 ? d : inner))(...args);
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns function supports the forms:
|
||||
* deco(target, key, desc) -> decorate a method
|
||||
* deco(Fn) -> call the decorator proxy on a function
|
||||
*/
|
||||
function decorator(fn) {
|
||||
return opt => (
|
||||
typeof opt==='function' ? fn(opt) : (target, key, desc) => {
|
||||
desc.value = fn(desc.value, opt, target, key, desc);
|
||||
}
|
||||
);
|
||||
}
|
37
tests/bind.js
Normal file
37
tests/bind.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { bind } from '..';
|
||||
import { expect } from 'chai';
|
||||
|
||||
/*global describe,it*/
|
||||
|
||||
describe('bind()', () => {
|
||||
it('should bind when used as a simple decorator', next => {
|
||||
let c = {
|
||||
@bind
|
||||
foo() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
expect(c.foo()).to.equal(c);
|
||||
|
||||
let p = c.foo;
|
||||
expect(p()).to.equal(c);
|
||||
|
||||
let a = {};
|
||||
expect(c.foo.call(a)).to.equal(c);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
it('should bind when used as a function', next => {
|
||||
let ctx = {},
|
||||
c = bind(function(){ return this; }, ctx);
|
||||
|
||||
expect(c()).to.equal(ctx);
|
||||
|
||||
let a = {};
|
||||
expect(c.call(a)).to.equal(ctx);
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
71
tests/debounce.js
Normal file
71
tests/debounce.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { debounce } from '..';
|
||||
import { expect } from 'chai';
|
||||
|
||||
/*global describe,it*/
|
||||
|
||||
describe('debounce()', () => {
|
||||
it('should debounce when used as a simple decorator', next => {
|
||||
let c = {
|
||||
calls: 0,
|
||||
args: null,
|
||||
|
||||
@debounce
|
||||
foo(...args) {
|
||||
c.calls++;
|
||||
c.args = args;
|
||||
c.context = this;
|
||||
}
|
||||
};
|
||||
|
||||
expect(c).to.have.property('calls', 0);
|
||||
c.foo(1);
|
||||
expect(c).to.have.property('calls', 0);
|
||||
c.foo(2);
|
||||
c.foo(3);
|
||||
setTimeout( () => {
|
||||
expect(c).to.have.property('calls', 1);
|
||||
expect(c.args).to.deep.equal([3]);
|
||||
expect(c.context).to.equal(c);
|
||||
|
||||
next();
|
||||
}, 20);
|
||||
});
|
||||
|
||||
it('should debounce when used as a function', next => {
|
||||
let c = debounce( (...args) => {
|
||||
m.calls++;
|
||||
m.args = args;
|
||||
}),
|
||||
m = { calls:0, args:null };
|
||||
|
||||
expect(m).to.have.property('calls', 0);
|
||||
c(1);
|
||||
expect(m).to.have.property('calls', 0);
|
||||
c(2);
|
||||
c(3);
|
||||
setTimeout( () => {
|
||||
expect(m).to.have.property('calls', 1);
|
||||
expect(m.args).to.deep.equal([3]);
|
||||
|
||||
next();
|
||||
}, 20);
|
||||
});
|
||||
|
||||
it('should support passing a delay', next => {
|
||||
let c = debounce(5, (...args) => {
|
||||
m.calls.push(args);
|
||||
}),
|
||||
m = { calls:[] };
|
||||
|
||||
c(1);
|
||||
setTimeout(()=> c(2), 1);
|
||||
setTimeout(()=> c(3), 10);
|
||||
setTimeout(()=> c(4), 14);
|
||||
setTimeout(()=> c(5), 22);
|
||||
expect(m.calls).to.have.length(0);
|
||||
setTimeout( () => {
|
||||
expect(m.calls).to.deep.equal([ [2], [4], [5] ]);
|
||||
next();
|
||||
}, 30);
|
||||
});
|
||||
});
|
17
tests/index.ts
Normal file
17
tests/index.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { bind, debounce, memoize } from '..';
|
||||
class C {
|
||||
@bind
|
||||
foo() { }
|
||||
|
||||
@debounce
|
||||
moo() { }
|
||||
|
||||
@debounce(1000)
|
||||
mooWithCustomDelay() { }
|
||||
|
||||
@memoize
|
||||
mem() { }
|
||||
|
||||
@memoize(true)
|
||||
memWithConfig() { }
|
||||
}
|
72
tests/memoize.js
Normal file
72
tests/memoize.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { memoize } from '..';
|
||||
import { expect } from 'chai';
|
||||
|
||||
/*global describe,it*/
|
||||
|
||||
describe('memoize()', () => {
|
||||
it('should memoize when used as a simple decorator', next => {
|
||||
let c = {
|
||||
@memoize
|
||||
foo(key) {
|
||||
c[key] = (c[key] || 0) + 1;
|
||||
}
|
||||
};
|
||||
|
||||
expect(c).not.to.have.property('a');
|
||||
c.foo('a');
|
||||
expect(c).to.have.property('a', 1);
|
||||
c.foo('a');
|
||||
c.foo('a');
|
||||
expect(c).to.have.property('a', 1);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
it('should memoize when used as a function', next => {
|
||||
let c = memoize( key => {
|
||||
m[key] = (m[key] || 0) + 1;
|
||||
}),
|
||||
m = {};
|
||||
|
||||
expect(m).not.to.have.property('a');
|
||||
c('a');
|
||||
expect(m).to.have.property('a', 1);
|
||||
c('a');
|
||||
c('a');
|
||||
expect(m).to.have.property('a', 1);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
it('should memoize when called without arguments', next => {
|
||||
let c = memoize( key => {
|
||||
m[key] = (m[key] || 0) + 1;
|
||||
}),
|
||||
m = {};
|
||||
|
||||
expect(m).not.to.have.property('undefined');
|
||||
c();
|
||||
expect(m).to.have.property('undefined', 1);
|
||||
c();
|
||||
c();
|
||||
expect(m).to.have.property('undefined', 1);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
it('should memoize when called with an empty string', next => {
|
||||
let c = memoize( key => {
|
||||
m[key] = (m[key] || 0) + 1;
|
||||
}),
|
||||
m = {};
|
||||
|
||||
expect(m).not.to.have.property('');
|
||||
c('');
|
||||
expect(m).to.have.property('', 1);
|
||||
c('');
|
||||
c('');
|
||||
expect(m).to.have.property('', 1);
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
24
tsconfig.json
Normal file
24
tsconfig.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "es2015",
|
||||
"target": "es2016",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2016"
|
||||
],
|
||||
"baseUrl": "./",
|
||||
"noImplicitAny": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": false,
|
||||
"moduleResolution": "node",
|
||||
"strictNullChecks": true,
|
||||
"declaration": true,
|
||||
"noEmit": true,
|
||||
"pretty": true,
|
||||
"outDir": "ts-output"
|
||||
},
|
||||
"include": [
|
||||
"src/decko.d.ts",
|
||||
"tests/index.ts"
|
||||
]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue