Giter Site home page Giter Site logo

jayphelps / core-decorators Goto Github PK

View Code? Open in Web Editor NEW
4.5K 76.0 262.0 245 KB

Library of stage-0 JavaScript decorators (aka ES2016/ES7 decorators but not accurate) inspired by languages that come with built-ins like @​override, @​deprecate, @​autobind, @​mixin and more. Popular with React/Angular, but is framework agnostic.

License: MIT License

JavaScript 99.87% TypeScript 0.13%

core-decorators's Introduction

WARNING: this library was made using the JavaScript stage-0 decorators spec, not the latest version (stage-2) which drastically changed in breaking ways. As such, it was and still is highly experimental and at this point probably best avoided. If/when the decorators spec ever becomes stage-3, and at least one JS compiler supports it, this repo will be updated to support that specification and then we can work towards a stable v1.0.0 version. In the meantime, this repo should mostly considered unmaintained, except for any security/critical issues, as any work done would mostly be thrown away.

core-decorators.js Build Status

Library of JavaScript stage-0 decorators (aka ES2016/ES7 decorators but that's not accurate) inspired by languages that come with built-ins like @​override, @​deprecate, @​autobind, @​mixin and more. Popular with React/Angular, but is framework agnostic. Similar to Annotations in Java but unlike Java annotations, decorators are functions which are applied at runtime.

These are stage-0 decorators because while the decorators spec has changed and is now stage-2, no transpiler has yet to implement these changes and until they do, this library won't either. Although the TypeScript documentation uses the phrase "Decorators are a stage 2 proposal for JavaScript" this is misleading because TypeScript still only implements the stage-0 version of the spec, which is very incompatible with stage-2 (as of this writing). If you concretely find that a compiler (babel, TS, etc) implement stage-2+, please do link me to the appropriate release notes! 🎈

*compiled code is intentionally not checked into this repo

Get It

A version compiled to ES5 in CJS format is published to npm as core-decorators

npm install core-decorators --save

This can be consumed by any transpiler that supports stage-0 of the decorators spec, like babel.js version 5. Babel 6 does not yet support decorators natively, but you can include babel-plugin-transform-decorators-legacy or use the applyDecorators() helper.

core-decorators does not officially support TypeScript. There are known incompatibilities with the way it transpiles the output. PRs certainly welcome to fix that!

Bower/globals

A globals version is available here in the artifact repo, or via $ bower install core-decorators. It defines a global variable CoreDecorators, which can then be used as you might expect: @CoreDecorators.autobind(), etc.

I highly recommend against using that globals build as it's quite strange you're using decorators (a proposed future feature of JavaScript) while not using ES2015 modules, a spec ratified feature used by nearly every modern framework. Also--bower is on its deathbed and IMO for very good reasons.

Need lodash utilities as decorators?

core-decorators aims to provide decorators that are fundamental to JavaScript itself--mostly things you could do with normal Object.defineProperty but not as easily when using ES2015 classes. Things like debouncing, throttling, and other more opinionated decorators are being phased out in favor of lodash-decorators which wraps applicable lodash utilities as decorators. We don't want to duplicate the effort of lodash, which has years and years of robust testing and bugfixes.

Decorators

For Properties and Methods
For Properties
For Methods
For Classes

Helpers

Docs

@autobind

Note: there is a bug in react-hot-loader <= 1.3.0 (they fixed in 2.0.0-alpha-4) which prevents this from working as expected. Follow it here

Forces invocations of this function to always have this refer to the class instance, even if the function is passed around or would otherwise lose its this context. e.g. var fn = context.method; Popular with React components.

Individual methods:

import { autobind } from 'core-decorators';

class Person {
  @autobind
  getPerson() {
  	return this;
  }
}

let person = new Person();
let { getPerson } = person;

getPerson() === person;
// true

Entire Class:

import { autobind } from 'core-decorators';

@autobind
class Person {
  getPerson() {
    return this;
  }

  getPersonAgain() {
    return this;
  }
}

let person = new Person();
let { getPerson, getPersonAgain } = person;

getPerson() === person;
// true

getPersonAgain() === person;
// true

@readonly

Marks a property or method as not being writable.

import { readonly } from 'core-decorators';

class Meal {
  @readonly
  entree = 'steak';
}

var dinner = new Meal();
dinner.entree = 'salmon';
// Cannot assign to read only property 'entree' of [object Object]

@override

Checks that the marked method indeed overrides a function with the same signature somewhere on the prototype chain.

Works with methods and getters/setters only (not property initializers/arrow functions). Will ensure name, parameter count, as well as descriptor type (accessor/data). Provides a suggestion if it finds a method with a similar signature, including slight misspellings.

import { override } from 'core-decorators';

class Parent {
  speak(first, second) {}
}

class Child extends Parent {
  @override
  speak() {}
  // SyntaxError: Child#speak() does not properly override Parent#speak(first, second)
}

// or

class Child extends Parent {
  @override
  speaks() {}
  // SyntaxError: No descriptor matching Child#speaks() was found on the prototype chain.
  //
  //   Did you mean "speak"?
}

@deprecate (alias: @deprecated)

Calls console.warn() with a deprecation message. Provide a custom message to override the default one. You can also provide an options hash with a url, for further reading.

import { deprecate } from 'core-decorators';

class Person {
  @deprecate
  facepalm() {}

  @deprecate('We stopped facepalming')
  facepalmHard() {}

  @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
  facepalmHarder() {}
}

let person = new Person();

person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.

person.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming

person.facepalmHarder();
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
//     See http://knowyourmeme.com/memes/facepalm for more details.
//

@debounce 🚫 DEPRECATED

Creates a new debounced function which will be invoked after wait milliseconds since the time it was invoked. Default timeout is 300 ms.

Optional boolean second argument allows to trigger function on the leading instead of the trailing edge of the wait interval. Implementation is inspired by similar method from UnderscoreJS.

import { debounce } from 'core-decorators';

class Editor {

  content = '';

  @debounce(500)
  updateContent(content) {
    this.content = content;
  }
}

@throttle 🚫 DEPRECATED

Creates a new throttled function which will be invoked in every wait milliseconds. Default timeout is 300 ms.

Second argument is optional options:

  • leading: default to true, allows to trigger function on the leading.
  • trailing: default to true, allows to trigger function on the trailing edge of the wait interval.

Implementation is inspired by similar method from UnderscoreJS.

import { throttle } from 'core-decorators';

class Editor {

  content = '';

  @throttle(500, {leading: false})
  updateContent(content) {
    this.content = content;
  }
}

@suppressWarnings

Suppresses any JavaScript console.warn() call while the decorated function is called. (i.e. on the stack)

Will not suppress warnings triggered in any async code within.

import { suppressWarnings } from 'core-decorators';

class Person {
  @deprecated
  facepalm() {}

  @suppressWarnings
  facepalmWithoutWarning() {
    this.facepalm();
  }
}

let person = new Person();

person.facepalmWithoutWarning();
// no warning is logged

@enumerable

Marks a method as being enumerable. Note that instance properties are already enumerable, so this is only useful for methods.

import { enumerable } from 'core-decorators';

class Meal {
  pay() {}

  @enumerable
  eat() {}
}

var dinner = new Meal();
for (var key in dinner) {
  key;
  // "eat" only, not "pay"
}

@nonenumerable

Marks a property as not being enumerable. Note that class methods are already nonenumerable, so this is only useful for instance properties.

import { nonenumerable } from 'core-decorators';

class Meal {
  entree = 'steak';

  @nonenumerable
  cost = 20.99;
}

var dinner = new Meal();
for (var key in dinner) {
  key;
  // "entree" only, not "cost"
}

Object.keys(dinner);
// ["entree"]

@nonconfigurable

Marks a property or method so that it cannot be deleted; also prevents it from being reconfigured via Object.defineProperty, but this may not always work how you expect due to a quirk in JavaScript itself, not this library. Adding the @readonly decorator fixes it, but at the cost of obviously making the property readonly (aka writable: false). You can read more about this here.

import { nonconfigurable } from 'core-decorators';

class Foo {
  @nonconfigurable
  @readonly
  bar() {};
}

Object.defineProperty(Foo.prototype, 'bar', {
  value: 'I will error'
});
// Cannot redefine property: bar

@decorate

Immediately applies the provided function and arguments to the method, allowing you to wrap methods with arbitrary helpers like those provided by lodash. The first argument is the function to apply, all further arguments will be passed to that decorating function.

import { decorate } from 'core-decorators';
import { memoize } from 'lodash';

var count = 0;

class Task {
  @decorate(memoize)
  doSomethingExpensive(data) {
    count++;
    // something expensive;
    return data;
  }
}

var task = new Task();
var data = [1, 2, 3];

task.doSomethingExpensive(data);
task.doSomethingExpensive(data);

count === 1;
// true

@lazyInitialize

Prevents a property initializer from running until the decorated property is actually looked up. Useful to prevent excess allocations that might otherwise not be used, but be careful not to over-optimize things.

import { lazyInitialize } from 'core-decorators';

function createHugeBuffer() {
  console.log('huge buffer created');
  return new Array(1000000);
}

class Editor {
  @lazyInitialize
  hugeBuffer = createHugeBuffer();
}

var editor = new Editor();
// createHugeBuffer() has not been called yet

editor.hugeBuffer;
// logs 'huge buffer created', now it has been called

editor.hugeBuffer;
// already initialized and equals our buffer, so
// createHugeBuffer() is not called again

@mixin (alias: @mixins) 🚫 DEPRECATED

Mixes in all property descriptors from the provided Plain Old JavaScript Objects (aka POJOs) as arguments. Mixins are applied in the order they are passed, but do not override descriptors already on the class, including those inherited traditionally.

import { mixin } from 'core-decorators';

const SingerMixin = {
  sing(sound) {
    alert(sound);
  }
};

const FlyMixin = {
  // All types of property descriptors are supported
  get speed() {},
  fly() {},
  land() {}
};

@mixin(SingerMixin, FlyMixin)
class Bird {
  singMatingCall() {
    this.sing('tweet tweet');
  }
}

var bird = new Bird();
bird.singMatingCall();
// alerts "tweet tweet"

@time

Uses console.time and console.timeEnd to provide function timings with a unique label whose default prefix is ClassName.method. Supply a first argument to override the prefix:

class Bird {
  @time('sing')
  sing() {
  }
}

var bird = new Bird();
bird.sing(); // console.time label will be 'sing-0'
bird.sing(); // console.time label will be 'sing-1'

Will polyfill console.time if the current environment does not support it. You can also supply a custom console object as the second argument with the following methods:

  • myConsole.time(label)
  • myConsole.timeEnd(label)
  • myConsole.log(value)
let myConsole = {
  time: function(label) { /* custom time() method */ },
  timeEnd: function(label) { /* custom timeEnd method */ },
  log: function(str) { /* custom log method */ }
}

@profile

Uses console.profile and console.profileEnd to provide function profiling with a unique label whose default prefix is ClassName.method. Supply a first argument to override the prefix:

class Bird {
  @profile('sing')
  sing() {
  }
}

var bird = new Bird();
bird.sing(); // Adds a profile with label sing and marked as run 1
bird.sing(); // Adds a profile with label sing and marked as run 2

Because profiling is expensive, you may not want to run it every time the function is called. Supply a second argument of true to have the profiling only run once:

class Bird {
  @profile(null, true)
  sing() {
  }
}

var bird = new Bird();
bird.sing(); // Adds a profile with label Bird.sing
bird.sing(); // Does nothing

Alternatively you can pass a number instead of true to represent the milliseconds between profiles. Profiling is always ran on the leading edge.

class Bird {
  @profile(null, 1000)
  sing() {
  }
}

var bird = new Bird();
bird.sing(); // Adds a profile with label Bird.sing
// Wait 100ms
bird.sing(); // Does nothing
// Wait 1000ms
bird.sing(); // Adds a profile with label Bird.sing

When you need extremely fine-tuned control, you can pass a function that returns a boolean to determine if profiling should occur. The function will have this context of the instance and the arguments to the method will be passed to the function as well. Arrow functions will not receive the instance context.

class Bird {
  @profile(null, function (volume) { return volume === 'loud'; })
  sing(volume) {
  }

  @profile(null, function () { return this.breed === 'eagle' })
  fly() {
  }
}

var bird = new Bird();
bird.sing('loud'); // Adds a profile with label Bird.sing
bird.sing('quite'); // Does nothing

bird.fly(); // Does nothing
bird.breed = 'eagle';
bird.fly(); // Adds a profile with label Bird.fly

Profiling is currently only supported in Chrome 53+, Firefox, and Edge. Unfortunately this feature can't be polyfilled or faked, so if used in an unsupported browser or Node.js then this decorator will automatically disable itself.

@extendDescriptor

Extends the new property descriptor with the descriptor from the super/parent class prototype. Although useful in various circumstances, it's particularly helpful to address the fact that getters and setters share a single descriptor so overriding only a getter or only a setter will blow away the other, without this decorator.

class Base {
  @nonconfigurable
  get foo() {
    return `hello ${this._foo}`;
  }
}

class Derived extends Base {
  @extendDescriptor
  set foo(value) {
    this._foo = value;
  }
}

const derived = new Derived();
derived.foo = 'bar';
derived.foo === 'hello bar';
// true

const desc = Object.getOwnPropertyDescriptor(Derived.prototype, 'foo');
desc.configurable === false;
// true

applyDecorators() helper

The applyDecorators() helper can be used when you don't have language support for decorators like in Babel 6 or even with vanilla ES5 code without a transpiler.

class Foo {
  getFoo() {
    return this;
  }
}

// This works on regular function prototypes
// too, like `function Foo() {}`
applyDecorators(Foo, {
  getFoo: [autobind]
});

let foo = new Foo();
let getFoo = foo.getFoo;
getFoo() === foo;
// true

Future Compatibility

Since most people can't keep up to date with specs, it's important to note that the spec is in-flux and subject to breaking changes. For the most part, these changes will probably be transparent to consumers of this project--that said, core-decorators has not yet reached 1.0 and may in fact introduce breaking changes. If you'd prefer not to receive these changes, be sure to lock your dependency to PATCH. You can track the progress of [email protected] in the The Road to 1.0 ticket.

Decorator Order Sometimes Matters

When using multiple decorators on a class, method, or property the order of the decorators sometimes matters. This is a neccesary caveat of decorators because otherwise certain cool features wouldn't be possible. The most common example of this is using @autobind and any Higher-Order Component (HOC) decorator, e.g. Redux's @connect. You must @autobind your class first before applying the @connect HOC.

@connect()
@autobind
class Foo extends Component {}

core-decorators's People

Contributors

avraammavridis avatar bramus avatar brigand avatar burtharris avatar dfsq avatar dtweedle avatar ephys avatar hashplus avatar ide avatar island205 avatar jayphelps avatar julien-f avatar karlhorky avatar kkwiatkowski avatar kwonoj avatar millette avatar nrdobie avatar pete-otaqui avatar prayagverma avatar sarahzinger avatar wuct avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

core-decorators's Issues

@throttle trailing edge not as expected

I expected that throttling a call with leading false, trailing true would finally issue the call using the parameters of the final call not the leading call.

i.e. if you are calling @Throttle(500, {leading: false, trailing: true}) _check(index) {...}

and rendering 23 consecutive rows rapidly, need to check if the final rendered index is high enough to trigger a fetch of more data.

it actually calls _check(0) instead of _check(23)

order of decorators causing some decorators not to apply

When combining e.g. autobind with react-css-modules the autobind decorator stops working, unless it is specified as the last decorator (closest to the class declaration). I.e. this makes autobind work:

@cssModules(Bootstrap, { allowMultiple: true })    
@autobind

But cssModules to fail. The following will make @autobind have no effect.

@autobind
@cssModules(Bootstrap, { allowMultiple: true })

Is this because of how @cssModules was implemented, or is @autobind also playing a role in this issue?

@throttle / @debounce - allow to `cancel` method call

It would be great to add functionality to cancel method execution

Currently I use @decorate on lodash.throttle and lodash.debounce to make it work

class EventHelper() {
    cancelChange() {
        this.triggerChange.cancel();
    }

    @decorate(_.debounce, 100)
    triggerChange() {
         // do something
    }
}

That's work just fine, but if i want to use @autobind on it, .bind removes .cancel() function from method.

So I had looked through @throttle and @debounce code and noticed that timeout ids are stored in class instance meta

So the fastest and dumbest solution will be:

debounce.cancel = function(instance, method) {
    const { debounceTimeoutIds } = metaFor(instance);

    let methodKey;

    for (let key in instance) {
        if (instance[key] === method) {
            methodKey = key;
            break;
        }
    }

    clearTimeout(debounceTimeoutIds[methodKey]);

    delete debounceTimeoutIds[methodKey];
}

and

class EventHelper() {
    cancelChange() {
        debounce.cancel(this, this.triggerChange);
    }

    @autobind
    @debounce(100)
    triggerChange() {
         // do something
    }
}

But that syntax can make people cry and worth discussion.

May be put it to utils as debounceCancel

Or make method argument as optional and clear all timeouts in meta

Or create another method in class that will cancel its debounce like triggerChange => __cancelTriggerChange (warn or throw exception if method exists)

Cannot run tests in Windows shell

Maybe it's because of the difference in operating systems, but the straight forward way of running the tests did not work. I forked & cloned the repo, then

> npm install
> npm install -g mocha #not sure if it's necessary, did it anyways
> npm test

Now I can see in the log, that the build completes fast & without errors, but the tests don't even start:

E:\core-decorators.js>npm test

> [email protected] test E:\core-decorators.js
> npm run build && mocha --compilers js:babel/register --require babel/polyfill 'test/**/*.spec.js'


> [email protected] build E:\core-decorators.js
> babel --out-dir lib src

src\autobind.js -> lib\autobind.js
src\core-decorators.js -> lib\core-decorators.js
src\debounce.js -> lib\debounce.js
src\decorate.js -> lib\decorate.js
src\deprecate.js -> lib\deprecate.js
src\enumerable.js -> lib\enumerable.js
src\memoize.js -> lib\memoize.js
src\mixin.js -> lib\mixin.js
src\nonconfigurable.js -> lib\nonconfigurable.js
src\nonenumerable.js -> lib\nonenumerable.js
src\override.js -> lib\override.js
src\private\utils.js -> lib\private\utils.js
src\readonly.js -> lib\readonly.js
src\suppress-warnings.js -> lib\suppress-warnings.js
src\throttle.js -> lib\throttle.js
E:\core-decorators.js\node_modules\mocha\lib\utils.js:626
        throw new Error("cannot resolve path (or pattern) '" + path + "'");
        ^

Error: cannot resolve path (or pattern) ''test/**/*.spec.js''
    at Object.lookupFiles (E:\core-decorators.js\node_modules\mocha\lib\utils.js:626:15)
    at E:\core-decorators.js\node_modules\mocha\bin\_mocha:316:30
    at Array.forEach (native)
    at Object.<anonymous> (E:\core-decorators.js\node_modules\mocha\bin\_mocha:315:6)
    at Module._compile (module.js:435:26)
    at Object.Module._extensions..js (module.js:442:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:311:12)
    at Function.Module.runMain (module.js:467:10)
    at startup (node.js:134:18)
    at node.js:961:3
npm ERR! Test failed.  See above for more details.

E:\core-decorators.js>

Now If I run tests individually, like

mocha --compilers js:babel/register --require babel/polyfill test\unit\autobind.spec.js

I get errors like this:

  1) @autobind returns a bound instance for a method:
     TypeError: Cannot read property 'equal' of undefined
      at Context.<anonymous> (E:/core-decorators.js/test/unit/autobind.spec.js:48:20)
      at callFn (C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:286:21)
      at Test.Runnable.run (C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:279:7)
      at Runner.runTest (C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:421:10)
      at C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:528:12
      at next (C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:341:14)
      at C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:351:7
      at next (C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:283:14)
      at C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:314:7
      at done (C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:247:5)
      at callFn (C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:301:7)
      at Hook.Runnable.run (C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:279:7)
      at next (C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:297:10)
      at Immediate._onImmediate (C:\Users\a\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:319:5)

...

Which is also super weird.

So, is there any specific thing I need to do before I can run the tests?

I'm on Windows 8.1 by the way using node 4.2.1, npm 2.14.7

Deprecate decorator

With recent changes (commit af4023c) deprecate decorator fails to pass message properly, it only uses the first character since ...arg converts string into array of characters.

browser version

It would be grate to use this module in browser with single file output.

P.S.
How about publishing to bower?

[suggestion] @chainable decorator for functions

Chaning is a common pattern I was wondering what stops from a method for creating chainables ?

Example

class Foo{
    @chainable
    bar(){
    }

    baz(){
    }
}

const f = new Foo();
f.bar().bar().bar().baz();

@enumerable?

The default property descriptor value for methods is enumerable: false. It seems there’s no @enumerable decorator currently.

Relatedly, @nonenumerable should probably not be listed as a method decorator, since in this role it wouldn't do anything; it is only meaningful as a decorator for ES7 class properties, which do default to enumerable: true currently.

Consider a rewrite of @readonly & @deprecate examples

It was brought to my attention on Twitter that two of the examples from the current README include gender and animal humour that causes offence. I've rewritten them in my article and would ask that we take into account the feelings of folks who may feel hurt by them. We're all human, but in case using my examples is of help.

@readonly rewritten to use a Meal/Dinner theme:

screen shot 2015-07-10 at 00 22 47

@decorate rewritten to use a Person -> Captain Picard theme:

import { deprecate } from 'core-decorators';

class Person {
  @deprecate
  facepalm() {}

  @deprecate('We stopped facepalming')
  facepalmHard() {}

  @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
  facepalmHarder() {}
}

let captainPicard = new Person();

captainPicard.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.

captainPicard.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming

captainPicard.facepalmHarder();
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
//     See http://knowyourmeme.com/memes/facepalm for more details.
//

^ Note: The facepalm here is more to represent my own embarrassment at not having caught this sooner. It's my fault for not having caught this in my article before publishing.

Create decorator to overcome limitations of ECMAScript concerning overriding setters/getters with inheritance

Original discussion found in #44

Possible names:

@getset
@inheritprop
@merge
@inherit
@inheritDescriptor
@mergeDescriptor
@mergeExistingDescriptor
@mergeProperty (@RReverser)
@defineProperty (@RReverser)


See https://phabricator.babeljs.io/T2449 and the limitation of the Javascript spec, which will not allow one to just override a getter or a setter of a given property of the base class but instead will discard any such getters and setters when missing.

In turn you will have to redeclare the getters and setters all over again, e.g.

class Base {
    get name() ...
    set name(v) ...
}

class Derived
{
     set name(v)...
}

Now, instantiating Derived will allow you to set the name property, but you cannot get the value back as there is no getter.

How about allowing one to

class Derived
{
   @override 
    set name(v)...
}

were @override would then fill in the missing getter, or setter, accordingly?

private & protected

Hey,

I do not know if you'd interested but I've released recently this https://github.com/wojtkowiak/private-decorator.
It works only without strict because it is based on function.caller therefore you can use them only with es2015-without-strict.
If you want you can include it in your library. Of course you do not have to explain if you do not want to 😄 Just wanted you to know.

Greets

Maybe add simple profiler?

Usage

class UserPage extends Controller {
  @Timer()
  actionHome() {
    this.render('user/home', { user: this.global.user });
  }
}

Console:

Run UserPage.actionHome()
End UserPage.actionHome(): 3.765ms

LestaD/Decorators#Timer

extendDescriptor must recursively check super classes/constructors for the first occurrence of 'key'

https://github.com/jayphelps/core-decorators.js/blob/master/src/extendDescriptor.js#L5

will result in error when deriving from a hierarchy of super classes where the most recently derived from super class does not have the specified own property.

E.g.

class Base
{
   get foo() {return this._foo;}
}

class Foo extends Base
{}

class Bar extends Foo
{
  @extendDescriptor
   set foo(value) {this._foo = 1;}
}

So, rather than just looking for super to have the specified property, it should recursively traverse the inheritance chain instead, returning the first matching own descriptor found.

See https://gist.github.com/silkentrance/21a7016933b0e7d7338f969ba928a457 for a first stab at this problem.

Return promises where appropriate

Some decorators can turn synchronous function asynchronous, such as @debounce and @throttle. It would be nice to be able to do something like:

class Editor {

  content = '';

  @debounce(500)
  updateContent(content) {
    this.content = content;
  }

  updateAndRender(content) {
    this.updateContent(content).then(() => Renderer.render(this));
  }
}

@abstract

What about @abtract decorator?

function abstract (target, name, descriptor) {
  descriptor.value = () => {
    throw new Error(`${target.constructor.name}.${name} not implemented.`)
  }
}

class A {
  @abstract
  method () {}
}

class B extends A {
  method () { console.log('h1') }
}

console.log(new B().method()) // h1
console.log(new A().method()) // error with message: A.method not implemented.

[React JS] Method has decorators, put the decorator plugin before the classes one

I tried to use this in React js

import React from 'react';
import {autobind} from 'core-decorators';

class Login extends React.Component {
    @autobind
    focus() {
        this.textInput.focus();
    }

    render() {
        return (
           <div>
                 <input type="text" ref={ (input) => {this.textInput = input} }/>
                 <input type="button" value="Focus the text input" onClick={this.focus} />
           </div>
        );
     }
}

It show error

Method has decorators, put the decorator plugin before the classes one

Please help me.

{
  "name": "react-test",
  "private": true,
  "scripts": {
    "start": "meteor run"
  },
  "dependencies": {
    "autobind-decorator": "^1.3.4",
    "babel-runtime": "^6.18.0",
    "bootstrap": "^3.3.7",
    "core-decorators": "^0.15.0",
    "graphql": "^0.9.1",
    "griddle-react": "^0.7.1",
    "jquery-validation": "^1.16.0",
    "meteor-node-stubs": "~0.2.3",
    "react": "^15.4.2",
    "react-addons-pure-render-mixin": "^15.4.2",
    "react-bootstrap": "^0.30.7",
    "react-breadcrumbs": "^1.5.2",
    "react-dom": "^15.4.2",
    "react-router": "^3.0.2",
    "react-router-bootstrap": "^0.23.1",
    "simpl-schema": "^0.1.0",
    "uniforms": "^1.9.0",
    "uniforms-bootstrap3": "^1.9.0"
  },
  "devDependencies": {}
}

Make autobind for classes configureable

I think there should be an optional parameter to exclude methods from autobind.

Good example: Using React
You dont want to rebind all react-component methods.

@autobind on classes doesn't work with Typescript

SyntaxError: @autoBind can only be used on functions, not: undefined

@autoBind
class AGreatComponent extends React.Component<{},{}> {
...
}

My guess is there's a problem in the class vs function detection algorithm:

https://github.com/jayphelps/core-decorators.js/blob/master/src/autobind.js

function handle(args) {
if (args.length === 1) {
return autobindClass(...args);
} else {
return autobindMethod(...args); <-- code is going here instead
}
}

I'm not sure how to fix it.

I love core-decorators by the way! Made my last project much cleaner. Thanks for your work!

apply/Decorators helper

Please consider pointing out that there is the excellent transformation plugin made by @loganfsmyth that will allow you to use decorators with babel 6.x.

I know that there might be some issues with core decorators, but these need to be fixed anyhow as soon as the new spec arrives.

Unexpected token error in SyntaxErrorReporter

I'm trying to integrate the facebook library into my project and it uses core-decorators. However, I'm getting this error when I load the library:

screen shot 2016-10-12 at 10 21 39 am

I'm using babel-preset-es2015 on stage 0. It seems that it doesn't like how the class fields are declared (without constructor). Anyway to fix this?

@decorate creates method shared by all instances

@decorate works on the prototype level - which is fast - but the proposed use of decorate(_.memoize) makes the memoization shared between object instances. As method calls are memoized by first argument, the changing this does not make any effect. This code will not work as expected:

class Foo {
  @decorate(_.memoize)
  foo() {
    return this
  }
}

let a = new Foo()
let b = new Foo()
expect(a.foo() !== b.foo()) // should be different, but it's the same

The same goes for any other use of @decorate. I think it would be beneficial to create a separate @decorateInstance or change how the original one works, as it's not intuitive right now. What do you think?

@extendDescriptor

In the README.md you refer to #extendDescriptor while it should read #extenddescriptor.

BTW, isn't this properties only?

BTW there are other failing links similar to this one.

Decorators aren't ES2016/ES7 but people seem to think they are

Because the decorators proposal pre-dates the change from ES6/ES7 to ES2015, ES2016, etc (yearly dated-based) and just general misunderstanding of how the new spec process works, many people erroneously know decorators as "ES7 decorators" or less common "ES2016 decorators" even though they did not make the cut for 2016.

The problem we have now is that if we remove these terms from the repo description, README, etc people might have a harder time finding this project and/or possibly not realizing what it is or that it's not a feature officially supported by any final spec.

That said, I would like to stop reinforcing these incorrect terms so I still say we remove them. Any objections?

Upgrade to latest, stage 2 spec

Latest spec was just approved to stage 2: http://tc39.github.io/proposal-decorators/

Babel may or may not start supporting this spec. It's a non-trivial change and compared to most specs to date, fairly complicated, so I wouldn't be surprised if they continue to be too scared of churn to implement it. https://phabricator.babeljs.io/T2645

That said, I think I'll start trying to implement this anyway mostly so I can see if I can support both the old and new specs in the same library. I'm skeptical that I can.

Circular dependency in lib/private/utils.js

Hi,

There's a circular dependency in this package and it's breaking my webpack build:

ERROR in Circular dependency detected:
node_modules/core-decorators/lib/private/utils.js -> node_modules/core-decorators/lib/lazy-initialize.js -> node_modules/core-decorators/lib/private/utils.js

Inline class decorators?

For your consideration.

Greetings! I work at Sencha and we have spent considerable time in recent months deciding how we will use decorators and how they could replace the various aspects of our Ext JS class system.

Because these features are class-level concerns, we are seeing this unfortunate pattern emerge:

    @decoratorA({
        // maybe 1-100 lines
    })
    @decoratorB({
       // another long thing (maybe some html template)
    })
    class MyComponent extends Component {
        // empty? yes... often!
    }

I would like to prose an inline alternative syntax that would be equivalent to the above:

    class MyComponent extends Component {
        @decoratorA {
            // maybe 1-100 lines
        }

        @decoratorB {
           // another long thing (maybe some html template)
        }
    }

Basically a decorator name followed by an object literal. This maintains the aesthetic flow of describing the contents of a class within its body and does not "bury the headline" so to speak (that a class is being declared here).

You could see the benefits of this approach better with some simple decorators like @internal (a debug aid to detect accidental name collisions) or @statics (for better compression) or @prototype (to put data on the class prototype):

    class MyComponent extends Component {
        @statics {
            create () { ... },
            // many more static methods
        }

        @prototype {
             answer: 42,
             defaultText: 'Hello world'
        }

        @internal {
            deepThought () { ... },
            moreDeepThoughts () { ... }
        }
    }

For what it's worth, you see this same top-heavy class pattern in Angular 2 as well. The large numbers of lines above the class keyword marginalize the class statement.

I look forward to your thoughts. If this is not the right place to post suggestions, apologies in advance and I would greatly appreciate a pointer to the proper place. Thanks!

Typscriptify?

Following from the twitter discussion -

I'm happy to do a PR to convert, at first pass doesn't look like it would be too much trouble.

Benefits:

  • no manual management / work duplication of type definitions - see https://github.com/Microsoft/TypeScript/wiki/Typings-for-npm-packages for more details on that - no TSD / DefinitelyTyped for consumers.
  • Type definitions for consumers authoring their apps in TS
  • No change for consumers using CJS/ES6 modules (TS transpiles to these formats)
  • Typescript actually supports decorators and class properties (!)

Drawbacks:

  • lose a couple of niceties in the current source - mainly the ES7 Object rest spread which isn't currently supported by Typescript, so that would require a bit o' refactoring.
  • can get a bit hairy with polymorphic typings, but I don't see this as a big issue really.

Discuss?

chaining multiple @decorate's fails in 0.12.1

in 0.12.0 this code works

"use strict";

import {decorate} from "core-decorators";

function decorator(fn) {
    return function inner(...args) {
        console.log("fn", fn);
        return fn.bind(this)(...args);
    };
}

const obj = new class {

    @decorate(decorator)
    @decorate(decorator)
    test(...args) {
        console.log(...args);
    }

};

obj.test();

in 0.12.1 it throws

test.js:43
                return fn.bind(this).apply(undefined, arguments);
                         ^

TypeError: Cannot read property 'bind' of undefined
    at _class.inner (test.js:8:10)
    at Object.<anonymous> (test.js:22:5)
    at Module._compile (module.js:413:34)

Not working in IE when used with TypeScript

I am using @autobind from core-decorators in a TypeScript codebase, and I am seeing errors when trying to execute my code in IE. It works in Chrome, and I have only tried reproducing the error in IE11, so far.

image

The issue appears to stem from the spread operator in autobind.js:

// original source code
function handle(args) {
  if (args.length === 1) {
    return autobindClass(...args);
  } else {
    return autobindMethod(...args);
  }
}

// the generated output, which I am consuming from npm
function handle(args) {
  if (args.length === 1) {
    return autobindClass.apply(undefined, _toConsumableArray(args));
  } else {
    return autobindMethod.apply(undefined, _toConsumableArray(args));
  }
}

// auto-generated function to handle spread operator
function _toConsumableArray(arr) {
  if (Array.isArray(arr)) {
    for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2;
  } else {
    // the offending line
    return Array.from(arr);
  }
}

The Array.from is the issue, since IE does not have support for it.

Thoughts?

Add annotations for stability index

I love your project. I thought of the same the first time I saw decorators. As you already support @deprecate (alias: @deprecated) I'd encourage you to support the whole set of stability indices major projects like Node.js and Rust use. Taken from Node:

Stability: 0 - Deprecated
This feature is known to be problematic, and changes are
planned.  Do not rely on it.  Use of the feature may cause warnings.  Backwards
compatibility should not be expected.

Stability: 1 - Experimental
This feature was introduced recently, and may change
or be removed in future versions.  Please try it out and provide feedback.
If it addresses a use-case that is important to you, tell the node core team.

Stability: 2 - Unstable
The API is in the process of settling, but has not yet had
sufficient real-world testing to be considered stable. Backwards-compatibility
will be maintained if reasonable.

Stability: 3 - Stable
The API has proven satisfactory, but cleanup in the underlying
code may cause minor changes.  Backwards-compatibility is guaranteed.

Stability: 4 - API Frozen
This API has been tested extensively in production and is
unlikely to ever have to change.

Stability: 5 - Locked
Unless serious bugs are found, this code will not ever
change.  Please do not suggest changes in this area; they will be refused.

It could look like this:

@deprecated
someFunction() {}

@experimental
someFunction() {}

@unstable
someFunction() {}

@stable
someFunction() {}

@frozen
someFunction() {}

@locked
someFunction() {}

I think Rust defaults to compile everything above and including 3 - Stable. For JavaScript I'd default to console.warn() to everything below 3 - Stable allowing the developer to opt-in to a different stability index by setting an environment variable for Node.js and a global variable for Browsers.

Personally I'd would also prefer to have just @deprecated and no alias to @deprecate. I prefer to be more strict here.

@time throws "TypeError: Illegal invocation"

Using the example from https://www.npmjs.com/package/core-decorators#time I get "TypeError: Illegal invocation" thrown in the console:

class Bird {
  @time('sing')
  sing() {
  }
}

I looked into the sourcecode, and believe that the issue is related to the following line:

const CONSOLE = konsole || CONSOLE_NATIVE;

It appears that konsole is always null (at least in this simple usecase), thus CONSOLE is always set to the native console. Later on you do the following:

const time = CONSOLE.time || CONSOLE_TIME;
const timeEnd = CONSOLE.timeEnd || CONSOLE_TIMEEND;

And because CONSOLE is pointing to the native console, the correctly bound CONSOLE_TIME and CONSOLE_TIMEEND are not used. If I remove CONSOLE.time(End) ||, no errors are thrown and times are outputted to the console.

@autobind doesn't work properly when calling super from the parent's parent (and probably higher up).

@autobind
class A {
    method() {
          console.log(this.test);
    }
}

@autobind
class B extends A {
}

@autobind
class C extends B {
    test = 'hello';

    method() {
           super.method();
     }
}

In that case, calling new C().method() logs undefined instead of 'hello'. Adding a method() definition to B (that calls super.method()) solves the problem and I'd rather not have to this. I understand this is an edge case (even more so than regular super calls that were pretty hard to solve from what I hear), but nonetheless, this isn't the expected behaviour.

Using autobind entire class with react-transform-catch-errors leads to `this.props` undefined in `render()` method

Here is the example

import React from 'react'
import {autobind} from 'core-decorators'

@autobind
export default class Test extends React.Component {
    handleClick() {

    }render() {
        console.log(this.props) // here props is undefined
        return (
            <button onClick={this.handleClick}>{this.props.text}</button>
        )
    }
}

Maybe we could add an option to autobind named dontBind
For example,

@autobind({dontBind: {
    'componentDidMount',
    'render'
    ...
}})

The Road to 1.0

Spec compliancy

  • Update to support both the old and new spec (coming in Babel 6)
  • Implement backward compatibility, if possible ???

Add the remaining tests

decorators

  • autobind
  • debounce
  • deprecate
  • nonconfigurable
  • nonenumerable
  • override
  • readonly
  • suppress-warnings

(private/utils)

  • decorate
  • isDescriptor
  • metaFor

Decide if aliases will continue?

I generally do not like aliases. I'd prefer to have all decorators named as a verb and remove the aliases.

  • @​deprecate (alias: @​deprecated)
  • @​mixin (alias: @​mixins)

Slim builds

  • Support rollup via jsnext:main
  • Allow imports of each decorator to not pull in entire library for browserify/webpack users. Most likely via postinstall or similar, aliasing files into the package root so they can import autobind from 'core-decorators/autobind.

autobind not properly working for child and parent classes

import { autobind } from 'lib/decorators';

class AwesomeClass {

  @autobind
  awesomeMethod() {
    console.log('parent called, AWESOME METHOD');
  }

}

class AwesomeChildClass extends AwesomeClass {

  @autobind
  awesomeMethod() {
    console.log('child called, AWESOME CHILD METHOD');
    super.awesomeMethod();
  }

}

let instance = new AwesomeChildClass();

for (var i = 0; i < 5; ++i) {
  console.log('invoking');
  instance.awesomeMethod();
  console.log('---');
}

The first time, both child and parent messages are logged to the console; the next 4 times, only the parent message is logged. Is this the expected behavior?

@property decorator

Hi!

It would be great to have a @property decorator which can be used as shorthand for getter/setter declaration:

import {property} from 'core-decorators';

class Item {
    @property
    name;

    _getName() {}  // getter

    _setName() {}  // setter
}

So the decorator could autobind instance methods _get${propertyNameInUpperCamelCase}() and _set${propertyNameInUpperCamelCase}() as getter and setter, i.e. in the example above it will be _getName() and _setName() methods.

If getter and setter are not exist it can be autogenerated. Also some customization for method names can be added, and it can add some __private field which can be used as private store for properties for autogenerated values.

More spec about assertArguments

I'd like to implment assertArguments decorator, but found little info about this.

Does it mean no spec is defined for this decorator, so I can write it the way I like?

IE8 Not supported

ERROR: ,Object.defineProperty(e,a.key,a)

i have import :
respond.min.js,
html5shiv.min.js,
jquery.min.js,

component/console-polyfill/0.2.2/index.js,
component/es5-shim/4.1.14/es5-shim.min.js,
component/es5-shim/4.1.14/es5-sham.min.js,
component/media-match/2.0.2/media.match.min.js

nonconfigurable doesn't usually work as one might expect due to bug in JS spec

Guessing this is due to an apparent oversight in the spec, since every browser I tried had this behavior.

In raw JS, to show the issue: REPL

function Foo() {}

Object.defineProperty(Foo.prototype, 'bar', {
  value: 1,
  configurable: false,
  writable: true
});

Object.defineProperty(Foo.prototype, 'bar', {
  value: 2
});
// You might expect this to error but it doesn't!

Instead, it only does what you would expect if the descriptor is writable: false or is a getter/setter combo (which doesn't have a writable prop in the descriptor) REPL

function Foo() {}

Object.defineProperty(Foo.prototype, 'bar', {
  value: 1,
  configurable: false,
  writable: false
});

Object.defineProperty(Foo.prototype, 'bar', {
  value: 2
});
// TypeError: Cannot redefine property: bar

However this also obviously makes the property readonly, which isn't always desired and not the same thing as being nonconfigurable.

var foo = new Foo();
foo.bar = 3;
// TypeError: Cannot assign to read only property 'bar' of object '#<Foo>'

Most examples I've seen demoing configurable: false omit the writable property all-together, which means it defaults to writable: false so I imagine most haven't noticed this quirk.

@autobind doesn't work correctly with react-hot-loader <= 1.3.0

I am using @autoBind together with React and Redux. It works in several classes, but I have one class that it doesn't work with. I tried eliminating a bunch of things like @connect but I can't make it work.

Here's an example of how I use it, but this does work:

import React from 'react'
import {autobind} from 'core-decorators'

export default class TestMe extends React.Component {
        test() {
           alert('whee it works')
        }
    @autobind
    doSomething() {
        this.test()
    }render() {
        return (
            <button onClick={this.doSomething}>clickme</button>
        )
    }
}

Can you think of anything that would make the autobind fail? Hot reloading, other decorators, bugs in Babel? What should a debug trace look like?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.