• This repository has been archived on 29/Sep/2020
  • Stars
    star
    4,516
  • Rank 9,440 (Top 0.2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 9 years ago
  • Updated over 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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.

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 {}

More Repositories

1

git-blame-someone-else

Blame someone else for your bad code.
Shell
10,058
star
2

nofux

Unpredictable state container for JavaScript apps.
TypeScript
308
star
3

react-observable-subscribe

<Subscribe> component to automatically consume observables declaratively in React JSX
JavaScript
135
star
4

multiline-template

Multiline tagged templates using a pipe |, followed by a space, to signal line start, no more crazy indent hacks.
JavaScript
55
star
5

ember-string-interpolate

Adds string interpolation as a computed property to Ember.js (i.e. no more unreadable getter concatenation)
JavaScript
46
star
6

sweet-async-await

Sweet.js macros to support the async/await ES7 proposed feature
JavaScript
36
star
7

web-retina-emulator

A tiny script for testing what your website looks like on a 2x pixel density (retina) display.
JavaScript
30
star
8

broccoli-babel-boilerplate

[NO LONGER MAINTAINED] A boilerplate, using Broccoli, to author JavaScript libraries in ES6/ES7++ but transpile and distribute using Babel.js
JavaScript
28
star
9

ember-computed-injection

Inject anything from your Ember container into any class as a property. (i.e. sort of like `needs` for everything else)
JavaScript
22
star
10

loading-attribute-eagle-polyfill

loading="eagle" attribute polyfill
21
star
11

wasm-anyref-demo

Playing around with WebAssembly (Wasm) anyref using JavaScript's GC as the allocator
WebAssembly
21
star
12

webpack-rxjs-externals

Generate all the RxJS v5 "externals" for your webpack config.
JavaScript
17
star
13

ember-computed-content-controller

A computed property for injecting a unique instance of a content controller (ObjectController, ArrayController, etc) into an Ember class instance. Useful for nested ArrayControllers, among other things.
JavaScript
14
star
14

jayscript

A very small subset dialect of JavaScript
JavaScript
11
star
15

npm-publish-scoped

Publish any package temporarily to your @user scope, while you wait for them cut a release. Useful when your PR is merged and you don't want to wait.
JavaScript
10
star
16

resume

Jay's resume, hand-written in WebAssembly text
WebAssembly
9
star
17

ember-model-batch

Batch multiple model requests into a single ajax call (ember-data or ember-model)
JavaScript
8
star
18

redux-transaction

JavaScript
8
star
19

rxjs-compatibility

Use RxJS v5 with v4 and vice versa
JavaScript
7
star
20

react-map-props

Transform component props before they're passed to your component
JavaScript
7
star
21

stackblur

A fast almost Gaussian Blur For Canvas by Mario Klingemann (bower mirror)
6
star
22

shady-dom

Shady under a tree, not sketchy.
6
star
23

react-webpack-boilerplate

NOTE: As of now, this is used mostly internally by our team so it'll have some things like Semantic UI, etc that you might not want.
JavaScript
6
star
24

ember-computed-smart-property

Ember Computed Properties without needing to provide dependent keys (it figures it out for you)
JavaScript
5
star
25

wazzup

WebAssembly codegen, inspired by Binaryen. NOT intended for use, it was just created to learn the spec better.
TypeScript
5
star
26

uikit-cpp-win32

Win32 C++ Application Framework inspired by UIKit
C++
4
star
27

wasmjs-binary-bundles

Experimenting with combining WebAssembly and JavaScript into a single file bundle
JavaScript
4
star
28

using-llvm-from-rust-to-generate-webassembly

Code for my Medium article "Using LLVM from Rust, to generate WebAssembly"
Rust
4
star
29

github-clone-pr

Clone a github PR easily using just the github.com PR URL
JavaScript
4
star
30

ember-relay

That whole `Promises` thing without even paying attention.
JavaScript
4
star
31

react-redux-transaction

JavaScript
3
star
32

shortcut.js

Add keyboard shortcuts that simulate an element being clicked
JavaScript
3
star
33

ember-data-json-patch-adapter

Adds support for JSON Patch (RFC 6902) updates
3
star
34

rxjs-babel-only-tree-shaking-example

JavaScript
3
star
35

stutter.js

JavaScript Preprocessor Library
JavaScript
2
star
36

string.interpolate.js

A generic string interpolation helper for JavaScript that is library agnostic.
JavaScript
2
star
37

talks

A list of the talks I can/will/have given
2
star
38

benlesh

JavaScript
2
star
39

core-decorators-artifacts

Built files from https://github.com/jayphelps/core-decorators.js
JavaScript
2
star
40

react-router-query-params

Better query params management for react-router users
1
star
41

g-example

JavaScript
1
star
42

wasm-cdn

1
star
43

nails

Node.js web application framework based on JavaScript ES6/ES7 features
1
star
44

graffiti

JavaScript
1
star
45

jayphelps

1
star
46

shadow-dom.css

a CSS reset class name you can add to emulate/polyfill being inside a Shadow DOM from the preventing outside styles from bleeding in.
CSS
1
star
47

stunning-palm-tree

EJS
1
star
48

graffiti-runtime

JavaScript
1
star
49

gatsby-dev-ssr-issue

JavaScript
1
star
50

cool-story-bro-theme

A minimalistic blue-steel colored theme for Apple Keynote that uses Bebas Neue.
1
star
51

ember-media

Ember-idiomatic views for <video> and <audio>
JavaScript
1
star
52

jest-event-demo

JavaScript
1
star