Skate is a web component library based on the custom element spec. It's focused on being a tiny, performant, syntactic-sugar for binding behaviour to elements, attributes and classes using a consistent, simple and declarative API.
HTML
<my-element></my-element>
JavaScript
skate('my-element', {
created: function () {
this.textContent = 'Hello, World!';
}
});
Result
<my-element>Hello, World!</my-element>
IE9+ and all evergreens.
You can download the source yourself and put it wherever you want. Additionally you can use Bower:
bower install skatejs
Or NPM:
npm install skatejs
Include either dist/skate.js
or dist/skate.min.js
.
UMD files are located in lib/
. Simply require
the lib/index.js
file by whatever means you have and use it in accordance with whatever loader you've chosen.
The Skate source is written using ES6 modules. If you're using a transpilation method, then you can import skate from 'src/index';
and use it in your projects as you would any ES6 module.
If you're still skating old school the dist
directory contains the compiled ES5 source. The compiled source does not use a module loader; everything will just work. Access Skate as a global with skate
.
You define a component by passing a component ID and definition to the skate()
function. The ID you specify corresponds to one of the following:
- Tag name
- Value of the
is
attribute - Attribute name
- Class name
The definition is an object of options that define your component.
skate('my-component', {
// Lifecycle Callbacks
//
// All lifecycle callbacks use `this` to refer to the component element.
// Called when the element is created.
created () {},
// Called when the element is attached to the document.
attached () {},
// Called when the element is detached from the document.
detached () {},
// Called when an attribute is created, updated or removed.
attribute (name, oldValue, newValue) {
if (oldValue === null) {
// created
} else if (newValue === null) {
// removed
} else {
// updated
}
},
// A function that renders a template to your element. Since this function is
// responsible for rendering the template, you can literally use anything you
// want here.
template () {
this.innerHTML = 'something';
},
// Event Listeners
events: {
// All direct and bubbled events.
click (e) {
// Refers to the component element.
this;
// Standard DOM event object.
e;
// Same as `this`.
e.delegateTarget;
},
// Restricted to events triggered only on the component element. Arguments
// are the same as above.
'click my-element' (e) {},
// Event delegation.
'click .something' (e) {
// Same as above.
this && e;
// Instead matches whatever `.something` is.
e.delegateTarget;
},
// Multiple handlers.
click: [
handler1,
handler2
],
// Focus and blur can be delegated, too.
'focus .something' () {},
'blur .something' () {},
// Using Shadow DOM selectors.
//
// *These are only supported if you have native Shadow DOM support or are
// using a polyfill.*
'click :host::shadow' () {},
'click ::shadow ::content' () {}
},
// Extending Elements
//
// Restricts a particular component to binding explicitly to an element with
// a tag name that matches the specified value. This value is empty by
// default.
//
// Depending on the component type, it behaves like so:
//
// - When applied to a custom element, the component ID is used to match the
// value of the element's `is` attribute and the element's tag name is
// matched against the value specified here. This conforms with the custom
// element spec.
//
// - When given to a component that binds to an element using an attribute,
// the value specified here must match the element's tag name.
//
// - When specified on a component that is bound using a class name, this
// value must match the element's tag name.
//
// - If the value is empty, then the component is not restricted at all.
extends: '',
// Custom Property Descriptors
properties: {
prop1: {
// Two-way binding to attributes. Changes to this property propagate to
// its corresponding attribute and changes to its corresponding attribute
// are propagated to the property.
//
// This defaults to `false`. If you specify `true` the property name is
// dash-cased and used as the attribute it should keep in sync with. If
// you specify a `string`, it is used as the attribute name.
attr: false,
// This property should be notified of changes when these dependencies
// are notified of changes.
//
// You may also specify dependencies as space-separated string.
deps: [
// This property's notify events will be triggered when its sibling
// `dependencyProperty` is notified of changes.
'dependencyProperty',
// This property's notify events will be triggered when the descendant's
// `deepDependencyProperty` notify events are triggered.
//
// At the time the dependency event bubbles up from the descendant, it
// must be accessible via `this.some.nested.child`. If it is not, then
// the dependency is ignored.
'some.nested.child.deepDependencyProperty'
],
// Custom getter. The return value is used as the property value when
// retrieved. If you don't specify a getter, the value that it was set as
// is returned regardless of if you've specified a setter.
//
// To make a property "readonly", specify a getter without a setter.
get () {},
// Whether or not to trigger events when the property changes. Defaults to
// `true`. If you do not want events triggered, set this to a falsy value.
//
// If this is truthy, when the property is changed it will trigger an
// event called `skate.property` if `notify` is `true`, or an event with
// the same name as `notify` if specified as a `String`.
//
// The event object for both events contains the following information
// in the `detail` property:
//
// - `name` - The property name.
// - `newValue` - The property's new value.
// - `oldValue` - The property's old value.
notify: true
// Custom setter. Set value as you see fit. Return value is ignored. If
// you don't specify a getter, then whatever `newValue` was passed in to
// the setter, is returned by the default getter. If you want to return
// a custom value, specify a getter.
set (newValue, oldValue) {},
// A function that coerces the value to another value. You can specify
// any function you want here. The return value is then stored internally
// and passed as `newValue` in the setter.
type: Boolean,
// This will be used as the initial value for the property. If you specify
// a function then it will be invoked and the return value will be used.
value: 'initial value'
}
},
// Custom Methods and Properties
//
// This behaves just like any prototype object does. All methods and
// properties are added to the element's prototype (native), or to the element
// instance during the `created` lifecycle (polyfill).
//
// It's recommended you use the `properties` option for all public-api
// properties, but nothing is stopping you from putting them here if you
// don't need the special behaviour of `properties`.
prototype: {
get someProperty () {},
someMethod () {}
},
// Component Types
//
// The binding methods this component supports. For example, if you specify
// the `type` as `skate.type.ELEMENT`, then the component will only be bound
// to an element whos tag name matches the component ID.
//
// - `ELEMENT` Tag name only.
// - `ATTRIBUTE` Attribute names.
// - `CLASSNAME` Class names.
type: skate.type.ELEMENT,
// Preventing FOUC
//
// The `resolved` and `unresolved` attributes allow you to style a resolved
// or unresolved component. You can changes these if you want to use different
// names.
// The attribute name to add after calling the `created` callback.
resolvedAttribute: 'resolved',
// The attribute name to remove after calling the `created` callback.
unresolvedAttribute: 'unresolved'
});
The component lifecycle consists several callbacks:
created
attached
detached
attribute
template
created () {}
The created
callback gets triggered when the element is created.
There are some differences when using native vs polyfilled support.
// native: called immediately (synchronous)
// polyfill: called immediately (synchronous)
myElement();
new myElement();
skate.create('my-element');
// native: called immediately (synchronous)
// polyfill: called when inserted to the DOM (asynchronous)
document.createElement('my-element');
// native: called immediately (synchronous)
// polyfill: called when the mutation observer fires (asynchronous)
document.body.innerHTML = '<my-element></my-element>';
// native: called immediately (synchronous)
// polyfill: called when <div> is inserted into the DOM (asynchronous)
document.createElement('div').innerHTML = '<my-element></my-element>';
In instances where an element is initialized asynchronously, or your definition is loaded after the element is already on the page, there may be a flash of un-styled content. For more information see Preventing FOUC.
If using native custom elements, the element may not have any children when the created
callback is invoked. You should not assume a specific structure exists. For more information see skate.ready().
// Will not have children.
document.createElement('my-el');
// Will have children.
document.body.innerHTML = '<my-el>child</my-el>';
document.createElement('div').innerHTML = '<my-el>child</my-el>';
attached () {}
The attached
callback is fired when the element is attached to the document
. It can be invoked more than once. For example, if you were to add and remove the same element multiple times.
The attached
callback is called synchronously in native custom elements and asynchronously when polyfilled.
detached () {}
The detached
callback is fired when the element is detached from the document
. It can be invoked more than once. For example, if you were to add and remove the same element multiple times.
The detached
callback is called synchronously in native custom elements and asynchronously when polyfilled.
attribute (name, oldValue, newValue) {
if (oldValue === null) {
// created
} else if (newValue === null) {
// removed
} else {
// updated
}
}
The attribute
callback is fired whenever an element attribute is created, updated or removed. Unlike the native attributeChangedCallback
, this gets fired for every attribute an element has by the time it is initialised.
Attribute handlers are notified synchronously. Instead of using mutation observers, setAttribute
and removeAttribute
are patched to notify the callback as soon as the change occurs. If your existing code uses timeouts to wait until after mutations happen to execute logic related to an attribute change, you don't need to worry about changing it. The only difference is you won't need to write async boilerplate anymore.
However, this means that updating the attribute instance directly is not supported. If you do the following, the attribute
callback will not be notified of the change. For example the following will not trigger the attribute
callback:
myElement.attributes.myAttribute.value = 'new value';
You must use the attribute methods instead:
myElement.setAttribute('myAttribute', 'new value');
template () {}
Since the template function is just a callback and it's up to you how you template the element, you can use any templating engine that you want.
For example, Handlebars:
template () {
var compiled = Handlebars.compile('<p>Hello, {{ name }}!</p>');
this.innerHTML = compiled(this);
}
A good way to reuse a template function is to simply create a function that takes a string and returns a function that templates that string. The following example will compile the HTML using Handlebars and when invoked it will take all the attributes on the element and pass them in to the compiled template function as the context. This way, you can use any of the attributes specified on the element.
function handlebarify (html) {
var compiled = Handlebars.compile(html);
return function () {
this.innerHTML = compiled(this);
};
}
Then you can use it in any component:
template: handlebarify('<p>Hello, {{name}}!</p>')
If you wanted to fully embrace Web Components, you could even use Shadow DOM:
function shadowify (html) {
return function () {
this.createShadowRoot().innerHTML = html;
};
}
Usage would be similar to the handlebarify
function:
template: shadowify('<p>Hello, <content></content>!</p>')
events: {
// All direct and bubbled events.
click (e) {
// Refers to the component element.
this;
// Standard DOM event object.
e;
// Same as `this`.
e.delegateTarget;
},
// Restricted to events triggered only on the component element. Arguments
// are the same as above.
'click my-element' (e) {},
// Event delegation.
'click .something' (e) {
// Same as above.
this && e;
// Instead matches whatever `.something` is.
e.delegateTarget;
},
// Multiple handlers.
click: [
handler1,
handler2
],
// Focus and blur can be delegated, too.
'focus .something' () {},
'blur .something' () {},
// Using Shadow DOM selectors.
//
// *These are only supported if you have native Shadow DOM support or are
// using a polyfill.*
'click :host::shadow' () {},
'click ::shadow ::content' () {}
}
You may use the ::shadow
pseudo-element when specifying a delegate selector to an event. Underneath, Skate will walk the parent hierarchy from e.path[0]
instead of e.target
to find a matching target.
Note:
- It's experimental because the spec may change. As the spec changes, this may also change.
- Only
::shadow
is supported. Combinators are not because of the proposed removal. - If you are using this in browsers that do not support it natively, you must include a polyfill capable of allowing you to use these selectors.
There's several different ways to construct an element.
var myElement = skate('my-element', {});
var myElementInstance = myElement();
skate('my-element', {});
var myElementInstance = skate.create('my-element');
While not the most elegant way, this serves as an ode to the spec.
var MyElement = skate('my-element');
var myElementInstance = new MyElement();
For each of the ways you can construct an element, Skate also allows you to pass a properties object to them. The properties object is used to hydrate property values for the element.
var props = { propname: 'propvalue' };
var myElementInstance = myElement(props);
var myElementInstance = skate.create('my-element', props);
var myElementInstance = new MyElement(props);
You may extend components using ES6 classes or your favorite ES5 library.
var XParent = skate('x-parent', {
static created () {
}
static get events {
return {
event1 () {}
}
}
});
var XChild = skate('x-child', class extends XParent {
static created () {
super.created();
}
static get events {
return class extends super.events {
event1 (e) {
super.event1(e);
}
event2 () {
}
};
}
});
Due to the semantics of ES6 classes, you must specify any non-prototype members as static. ES6 classes also do not support the object literal syntax. In order to specify properties, just use the getter syntax like we did with events
above.
Skate gives you the option to specify custom properties and methods on your component.
skate('my-component', {
prototype: {
callMeLikeAnyNativeMethod: function () {
}
}
});
These members are applied directly to the element instance that your component is bound to so you can do stuff like this:
document.getElementById('my-component-id').callMeLikeanyNativeMethod();
Due to the fact that Skate uses Mutation Observers - and polyfills it for older browsers - elements are processed asynchronously. This means that if you insert an element into the DOM, custom methods and properties on that element will not be available right away. This will not work:
document.body.innerHTML = '<my-component id="my-component-id"></my-component>';
document.getElementById('my-component-id').someCustomMethod();
This is because the component will not be processed until after the block this code is in releases control back to the JavaScript engine. If you need to use the element right away, you must explicitly initialise it in a synchronous manner using skate.init()
:
var element = document.getElementById('my-component-id');
skate.init(element);
element.someCustomMethod();
This is very useful during testing, but can be used for any use case that requires synchronous operation.
The following are all available on the skate
object, or available for use from the src/api
or lib/api
folders.
Returns a function that attempts to make all arguments passed in callable. The arguments and context passed to the returned function are forwarded, so it can be used to compose behaviour. The context passed to the proxy function is returned.
function increment () {
++this.calls;
}
var proxy = skate.chain(
// Strings point to a method on the context.
'method',
// Functions are invoked and passed the context.
increment,
// A new chain is created from array values.
[ 'method', increment ],
// A new chain is created from object values.
{ key1: 'method', key2: increment },
// Same as both the array and object forms.
skate.chain('method', increment);
);
var context = proxy.call({
calls: 0,
method: function () {
++this.calls;
}
});
// 8
console.log(context.calls);
This makes it really simple to compose functionality:
function sharedFunction (e) {}
skate('my-element', {
events: {
click: skate.chain(
'instanceMethod',
sharedFunction,
function (e) {}
)
},
prototype: {
instanceMethod (e) {}
}
});
You could do all this with in a function, but it's nice not to have to worry about passing context and arguments. Everything is automatically invoked with the same context and arguments:
function sharedFunction (e) {}
skate('my-element', {
events: {
click: function (e) {
this.instanceMethod(e);
sharedFunction.call(this, e);
(function (e) {}(e));
}
},
prototype: {
instanceMethod (e) {}
}
});
Creates an element for the specified component name
, ensures that it's synchronously initialized and assigns all props
to it. On the surface, this doesn't appear much different than document.createElement()
in browsers that support custom elements, however, there's several benefits that it gives you on top of being a single, consistent and convenient way to do things in any browser and environment.
For example, this can be called in any browser and it will behave consistently:
skate.create('my-element');
In browsers that support custom elements, it is equivalent to:
document.createElement('my-element');
In browsers that do not support custom elements, you would have to manually ensure that the element is initialised synchronously:
var element = document.createElement('my-element');
skate.init(element);
To take this example further, if we've extended an element:
skate('my-element', {
extends: 'div'
});
How we call this function does not change:
skate.create('my-element');
However, in native land this does change:
document.createElement('div', 'my-element');
And in polyfill land, it's much different:
var element = document.createElement('div');
element.setAttribute('is', 'my-element');
skate.init(element);
Both the native and polyfilled examples above expose too many implementation details. It's much better to have one simple and consistent way to create an element.
If you have access to the function / constructor returned from the skate()
call, invoking that does the same exact thing as skate.create()
:
var myElement;
var MyElement = skate('my-element', {});
// Same thing:
myElement = skate.create('my-element');
myElement = MyElement();
myElement = new MyElement();
All methods of constructing an element support passing properties.
myElement = skate.create('my-element', { prop: 'value' });
myElement = MyElement({ prop: 'value' });
myElement = new MyElement({ prop: 'value' });
Passing properties automatically assigns them to the element:
// 'value'
console.log(myElement.prop);
Skate is designed to work with multiple versions of itself on the same page. If one version patches document.createElement()
differently than another, then you have problems. Even if we did do this, how document.createElement()
is called still depends on how the corresponding component has been registered, which is bad, especially when we can infer that information from the component definition.
Emits a CustomEvent
on element
that bubbles
and is cancelable
by default. This is useful for use in components that are children of a parent component and need to communicate changes to the parent.
skate('x-tabs', {
events: {
selected: hideAllAndShowSelected
}
});
skate('x-tab', {
events: {
click: function () {
skate.emit(this, 'selected');
}
}
});
It's preferrable not to reach up the DOM hierarchy because that couples your logic to a specific DOM structure that the child has no control over. To decouple this so that your child can be used anywhere, simply trigger an event.
You can emit more than one event at once by passing a space-separated string or an array as the eventName
parameter:
skate.emit(element, 'event1 event2');
skate.emit(element, [ 'event1', 'event2' ]);
The native element.dispatchEvent()
method returns false
if the event was cancelled. Since skate.emit()
can trigger more then one event, a Boolean
return value is ambiguous. Instead it returns an Array
of the event names that were canceled.
If you don't want the event to bubble, or you don't want it to be cancelable, then you can specify those options in the eventOptions
argument.
skate.emit(element, 'event', {
bubbles: false,
cancelable: false
});
Same as above except that it makes forwarding events simpler by returning a function that uses this
as the element
and calls skate.emit(element, eventName, eventOptions)
. Using this form, the x-tab
component's click
handler from the example above could be simplified as:
click: skate.emit('selected')
[soon]
[soon]
Same as what you'd come to expect from most libraries that offer a global namespace. It will restore the value of window.skate
to the previous value and return the current skate
object.
var currentSkate = skate.noConflict();
Defines the specified propertyName
using propertyDefinition
on the element
. The property definition may contain the following options.
Whether or not to link the property to an attribute. If true
, then the property name will be converted from camelCase
to dash-case
and the result will be used as the linked attribute.
attr: true
If a String
, then that will be used as the linked attribute exactly as it is specified.
attr: 'my-attribute-name`
No attribute is linked by default.
A space-separated String
or Array
of property names that this property depends on. If any of these properties change, then this property's setter will be invoked.
deps: 'dependency1 dependency2'
Or:
deps: [ 'dependency1', 'dependency2' ]
You can also specify dependencies on nested components by giving the dependency a dot-separated path.
deps: 'my.descendant.dependencyProperty`
If you do this, there are a couple requirements that must be met:
- The path (
my.descendant
in the above example) must be accessible from the element in which you're defining the property. - The name (
dependencyProperty
in the above example) must be defined as a property onmy.descendant
and notifying turned on.
The custom getter for this property. If not specified, then the value is stored internally when the value is set and returned whenever it is retrieved. The element is passed as this
.
If you want to make a property read-only, then specify get
without set
.
Whether or not to emit an event when the property is set. If true
, then a skate.property
event is emitted. If a String
, then the value is used as the event name that is emitted.
The custom setter for this property. The return value is ignored, so the logic in this method is responsible for setting the value however it needs to. The element is passed in as this
and it receives two arguments: newValue
and oldValue
, in that order.
set: function (newValue, oldValue) {
this.someOtherValue = oldValue + newValue;
}
Responsible for coercing the value before the setter is called. The return value of this function is what is passed as newValue
into the setter.
type: Number
You can use type
in conjunction with attr
to make a boolean attribute:
attr: true,
type: Boolean
When the property is passed a truthy value, the attribute is added and void of a value. When passed a falsy value, the attribute is removed.
The initial value for the property. It will be coerced and pass through the setter when initialised.
value: 'initial value'
If you specify a Function
then it will be called and the return value used as the initial value.
value: function () {
return 'initial value';
}
The element is not passed as this
because property initialisation order is not guaranteed. If you need the element in order to compute the value, it's better to use get
or set
instead.
If you wanted to have a property linked to a boolean attribute and have it set on the element by default, all you'd have to do is:
attr: true,
type: Boolean,
value: true
A way to define multiple property definitions to an element
at once. The propertyDefinitions
argument is an object who's keys are the property names and values are the respective property definitions.
Executes callback
when all components are loaded and all elements are upgraded. This comes in handy inside a component when it requires descendants to be upgraded before it uses them.
For example, the following may not work because parents are upgraded before descendants in native custom elements:
skate('x-parent', {
created: function () {
this.querySelector('x-child').sayHello();
}
});
skate('x-child', {
prototype: {
sayHello: function () {
console.log('hello');
}
}
});
But wrapping the call to sayHello()
will:
skate.ready(() => this.querySelector('x-child').sayHello());
Contains the constants for each type of binding that Skate supports. They are:
skate.type.ELEMENT
- Bind to a tag name.skate.type.ATTRIBUTE
- Bind to an attribute name.skate.type.CLASSNAME
- Bind to a class name.
Returns the current version of Skate.
A convenient wrapper around Skate's internal MutationObserver
. It allows you to watch an element for added or removed nodes.
skate.watch(element, function (added, removed) {
console.log(added.length);
console.log(removed.length);
});
If you want to listen for changes to descendants, pass the subtree
option:
skate.watch(element, callback, { subtree: true });
Currently, no other options besides subtree
are supported.
Skate uses the Custom Element spec as a guideline but it does not polyfill it, nor does it polyfill the behaviour of ShadowDOM or HTML Imports.
You can do some pretty cool things with Skate that you can't do with Web Components. For example, you can write polyfills for existing elements:
<datalist>...</datalist>
:
skate('datalist', {
created: polyfillDatalistElement
});
<input placeholder="">
:
skate('placeholder', {
extends: 'input',
type: skate.type.ATTRIBUTE,
created: polyfillInputPlaceholder
});
<input type="date">
:
skate('type', {
extends: 'input',
type: skate.type.ATTRIBUTE,
attributes: {
type: function (element, change) {
if (change.newValue === 'date') {
makeIntoDatepicker(element);
}
}
}
});
<link rel="import" href="path/to/import.html">
(HTML Imports):
skate('rel', {
extends: 'link',
type: skate.type.ATTRIBUTE,
attributes: {
rel: function (element, change) {
if (change.newValue === 'import') {
makeIntoHtmlImport(element);
}
}
}
});
Because Skate can also bind to attributes and classes, it offers a way to transition away from jQuery-style plugins to web components.
jQuery(function ($) {
$('.tabs').tabs();
});
There's several problems with this approach. First, you're running a selector against the document. This is unnecessary and can get slow in large DOMs even in the latest browsers. Second, it only gets executed on DOMContentLoaded
. If you want to dynamically add some tabs to your document, then you've got to manually call that again once they've been added to the DOM.
With Skate, those problems vanish. No selectors are run and your tabs will automatically be initialised regardless of when they are put into the document.
To refactor that into a Skate component, all you need to do is:
skate('tabs', {
type: skate.type.CLASSNAME
created: function () {
jQuery(this).tabs();
}
});
Possibly the best part about this is that you don't need to touch any markup and only a minimal amount of JavaScript.
If your component is bound via custom tags and your browser supports custom elements then Skate will use the native DOM implementation instead of using Mutation Observers which will have added performance benefits. This all happens underneath the hood and the API does not change.
As you may know, the only way to polyfill Mutation Observers is to use the deprecated DOM 3 Mutation Events. They were deprecated because if you insert 5k elements at once, you then trigger 5k handlers at once. Mutation Observers will batch that into a single callback.
Skate mostly polyfills Mutation Observers, but only internally. It is not usable outside of Skate since it only polyfills what Skate needs to function. Its code is written to withstand an extreme number of incoming DOM elements. It's fast in Internet Explorer, not just modern browsers, and this is what sets it apart from other polyfills. It also ensures that mutation events are queued, rather than executed as they come in. Once queued, they are batched into a single callback.
An element may not be initialised right away. To prevent FOUC, you can add the unresolved
attribute to any web component element and then use that attribute to hide the element in your stylesheets.
<style>
[unresolved] {
opacity: 0;
}
</style>
<my-element unresolved></my-element>
The unresolved
attribute will be removed after the created()
callback is called and before the attached()
callback is called.
Additionally, after removing the unresolved
attribute, Skate will add the resolved
attribute. This allows you to transition your styles:
[resolved] {
opacity: 1;
transition: opacity .3s ease;
}
If you have a DOM tree that you don't want Skate to polyfill then you can add the data-skate-ignore
attribute. This is ideal for mitigating performance issues associated with older browsers and inspecting each element that is added to the document. Generally this is only an issue in Internet Explorer and dealing with hundreds of thousands of elements. If your browser natively supports Custom Elements then this attribute is ignored.
<div data-skate-ignore>
<!-- Everything including the container will be ignored. -->
</div>
Skate has a noConflict()
method that we have come to expect from libraries that may come into conflict with the same name, or multiple versions of itself. It returns the new skate
while restoring the global skate
to the previous value.
var mySkate = skate.noConflict();
On top of offering a no-conflict mode, Skate plays well with multiple versions of itself on the same page. Prior to version 0.11
Skate did not share a registry or mutation observers. 0.11
and later share a registry and a mutation observer. This means that trying to register the same component in 0.11
and 0.12
would result in an error. Sharing a mutation observer ensures that we don't have more than main mutation observer on the page scanning incoming elements which helps with performance.
The .editorconfig
, .jscs
and .jshint
configs are all set up. If you can, enable these in your editor of choice.
To get a dev environment up and running, all you should need to do is run:
npm install
To run tests:
npm test
If you want to keep the Karma server alive to run them in your browser of choice:
npm test -- --watch
To run tests in a specific browser:
npm test -- --browsers Chrome,Firefox
To lint your files with jscs
and jshint
:
npm run lint
To build the distribution all you have to do is run:
npm run dist
This will build dist/skate.js
and dist/skate.min.js
. Don't worry about doing this in a PR; it'll avoid conflicts.
To build the lib
(ES5 + UMD) files:
npm run lib
To release all you've got to do is run npm release
. You can either specify the release type
, or tag
.
npm run release -- --tag x.x.x
Or:
npm run release -- --type minor
To deploy the documentation, run the following command from the branch or tag which you want to deploy:
npm run deploy
- Trey Shugart (author), Atlassian
The MIT License (MIT)
Copyright (c) 2014 Trey Shugart
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.