JSPM

  • Created
  • Published
  • Downloads 9
  • Score
    100M100P100Q69577F
  • License MIT

Client-side JavaScript framework for Single Page Applications

Package Exports

  • slingjs

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (slingjs) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

Sling update history

Sling

Sling is a client-side JavaScript framework for building Single Page Applications (SPAs). Sling is lightweight, 7.7KB minified, and less than 2.8KB gzipped.

Sling creates and uses a virtual DOM to perform differential updates for fast rendering.

Sling has an automatic change detection mechanism which updates your components for you.

Sling is structured using ECMAScript modules so that Sling code is tree shakeable to ultimately reduce bundle sizes.

Goals

Next Billion Users (NBUs) Empower developers to create SPAs for the NBUs of the web. The NBUs tend to use more affordable and less powerful devices. These devices struggle to achieve a two second Time to Interactive (TTI) with larger component libraries and frameworks.

Practical Familiarity with other JavaScript component libraries. Components are instantiated objects which may be used to control their own state. Components have a simple markup language with a gradual learning curve.

Generalized API as unopinionated as possible. Developers choose the right design patterns for their SPAs—not the library.

Fast High performance. Sling aims to get your SPA to interactive as quickly as possible and aims to keep your SPA as responsive as possible by staying within small code production budgets. With Sling, it should be easier for your SPA to run at 60 frames per second for a native application experience.

Minimal Setup Simply import the Sling functions required by your SPA and Sling is ready to use. No configuration files and no hidden requirements.

Testing

Run npm run devServer after a npm install to start webpack-dev-server.

Then navigate to localhost:8080/todo.html.

Performance (Time)

Because Sling is so lightweight, it can render thousands more nodes than Angular can in the same time period. Both test applications were served using the local-web-server NPM package.

Version Number of nodes created Average time Nodes per ms
Sling Core 4.2.0 (w/Routing) 1,000 23.414ms 42.709
Sling Core 4.1.0 (w/Routing) 1,000 32.502ms 30.767
Mithril.js 2.0.4 1,000 39.312ms 25.437
Sling Core 4.0.2 (w/Routing) 1,000 47.612ms 21.003
Angular 10.1.6 (w/Routing) 1,000 165.810ms 6.030

Sling.js creates nodes 5.102 times faster than Angular.

The above nodes were <p> tags containing strings generated by the following simple function:

for (let i = 0; i < 1000; ++i) {
    const value = i / 100;
    let str = val.toString(36).substring(7);
    this.data.push(str);
}
Version Number of nodes changed Average time Nodes per ms
Mithril.js 2.0.4 1,000 23.600ms 42.372
Sling Core 4.2.0 (w/Routing) 1,000 35.893ms 27.860
Sling Core 4.1.0 (w/Routing) Manual Change Detection 1,000 54.448ms 18.366
Sling Core 4.0.2 (w/Routing) Manual Change Detection 1,000 75.682ms 13.213
Sling Core 4.0.2 (w/Routing) 1,000 121.982ms 8.197
Sling Core 4.1.0 (w/Routing) 1,000 100.556ms 9.944
Angular 10.1.6 (w/Routing) 1,000 362.536ms 2.758

Sling.js changes nodes 6.659 times faster than Angular in manual change detection mode and changes nodes 3.605 times faster than Angular in automatic change detection mode.

The above nodes were <p> tags containing strings generated by the following simple function:

for (let i = 0; i < 1000; ++i) {
    const value = i / 50;
    let str = val.toString(36).substring(4);
    this.data.push(str);
}

Performance (Network)

Using simulated 3G network speeds, Sling Core 3.4.0 with routing loads 2.16 times faster than an Angular 10.1.6 project and 2.90 times faster than an Angular 10.1.6 project with routing.

Version Requests Async Time (3G network) Total
Sling Core 3.4.0 1 2.18s 4.20KB
Sling Core 3.4.0 (w/Routing) 1 2.24s 5.10KB
Angular 9.0.7 3 4.88s 144.10KB
Angular 10.1.6 3 4.84s 143.00KB
Angular 10.1.6 (w/Routing) 3 6.51s 229.00KB

Add Sling

To add Sling to your project, simply import the Sling function required by your application.

Below is an example of Sling import statements:

import { setState, mount, setDetectionStrategy, addRoute } from './sling';
import { Observable } from './sling-reactive';
import { slGet } from './sling-xhr';

Compatibility

Sling uses ES2015/ES6 syntax. Sling does not have any production dependencies.

Components

A component is a JavaScript class with a view() function that returns markup to render.

Components may be nested, but lifecycle hooks for nested components will not be automatically called. This is done for performance reasons and to stay within production code budgets.

Example component:

class HelloWorldComponent {
    constructor() {
    }

    view() {
        return markup('h1', {
            children: [
                innerText('Hello, world!')
            ]
        });
    }
}

Change Detection

Sling supports two change detection strategies: automatic and manual. The default mode is automatic.

Strategy Description
s.CHANGE_STRATEGY_AUTOMATIC Automatically update components after browser events and requests. This is the default setting.
s.CHANGE_STRATEGY_MANUAL Manually update components after browser events and requests.

Automatic change detection performs updates upon the following:

  • All browser events (click, mouseover, keyup, etc.)
  • setTimeout and setInterval
  • XMLHttpRequest and Fetch API requests

Automatic change detection does not perform updates upon the following:

  • Websocket events
  • IndexedDB callbacks

For versions of setTimeout and setInterval that do not trigger automatic change detection, use the following:

  • s.DETACHED_SET_TIMEOUT()
  • s.DETACHED_SET_INTERVAL()

For example:

s.DETACHED_SET_TIMEOUT(() => {
    console.log('Hello, world!');
}, 0);

Lifecycle Hooks

Components may specify up to three lifecycle hooks:

Lifecycle Hook Triggers Change Detection Timing
slOnInit() false Before the component is mounted to the DOM.
slOnDestroy() false Before the component is removed from the DOM.
slAfterInit() true After the component is mounted to the DOM.

Directives

Structural directives modify interactions with the DOM layout.

Directive Type Behavior
slUseExisting Structural Create the element or, if it exists, use the existing element.
slNoChanges Structural Only perform change detection on element's children.

Example directive usage:

view() {
    return markup('div', {
        attrs: {
            id: 'divSheetContent'
        },
        children: [
            new SelectedPartHeaderComponent().view(),
            markup('div', {
                attrs: {
                    id: 'chartDiv',
                    slUseExisting: 'true',
                    style: 'width: 90vw;'
                }
            })
        ]
    })
}

Core API

setState

void setState ( newStateObj )

Set a new state object for SPA.

getState

object getState ( )

Get the state object for SPA.

markup

object markup ( tagString, { attrs: {}, children: [] } )

Returns markup object to render. May be mounted to DOM.

Example markup call:

markup('div', {
    attrs: {
        style:  "width:50%;margin:auto;padding:1rem;"
    },
    children: [
        ...Array.from(getState().getNotes(), (note) =>
            markup('div', {
                attrs: {
                    class:  'input-group mb-3 animEnter',
                    style:  'width:100%;'
                },
                children: [
                ]
            })
        )
    ]
});

m

object markup ( tagString, { attrs: {}, children: [] } )

Terse alias for markup() function.

innerText

string innerText( text )

Set the inner text of a node.

Example innerText call:

innerText('Click me!');

spanWithText

object spanWithText( text )

Construct a span element with the given text.

Example spanWithText call:

spanWithText('Toggle');

mount

element mount ( rootElementId, component, attachDetector = true )

Mounts component on element with ID rootElementId in DOM. Returns root element in DOM where component was added.

Mounted components replace the element with rootElementId to avoid an excessive DOM size. Mounted components must have the same root element ID as the element in the DOM they are attached to.

By default, the Sling change detector is attached for the mounted component. Setting attachDetector to false prevents the change detector from being attached to this component. There are two convenience constants for change detection which are as follows:

Constant Value
s.CHANGE_DETECTOR_DETACHED false
s.CHANGE_DETECTOR_ATTACHED true

update

void update ( rootElementId, component )

Updates the component mounted at element with ID rootElementId.

version

string version( )

Returns Sling version number represented as a string.

Example:

console.log(s.version); // '3.2.0'

resolveAll

object resolveAll( promiseArray )

Returns an object with data about settled Promises in the format:

{ result: Promise Result | null, error: Error | null, status: 'fulfilled' | 'rejected' }

Example:

const requestPromises = [
    fetch('todo.html'), 
    fetch('http://does-not-exist')
];

resolveAll(requestPromises).then((results) => {
    const successfulPromises = results.filter(p => p.status === 'fulfilled');
});

Core Router API

addRoute

void addRoute ( hashUrlRegEx, { root: elementId, routeObj: object })

Define a hash-based route that will replace element with ID elementId's content with the specified component on route action.

Below is a list of possible routeObj properties:

Property Description
root The id of the element to replace on route.
component The component to replace root.
authGuard A function that returns true if route action may be taken, otherwise false.
authFail Object with route property to route to on authGuard fail. Also may specify params and attachDetector.

Example route definition:

addRoute('all', { component:  new  TodoListComponent(), root:  'divTodoList' });
addRoute('completed', { component:  new  TodoListCompletedComponent(), root:  'divTodoList' });
addRoute('user/:userId', { component: new UserProfileComponent(), root: 'divUserProfile' });

Example authGuard definition:

route('completed', { component:  new  TodoListCompletedComponent(), root:  'divTodoList', authGuard: function(proposedRoute) { console.log('This will prevent route to \'completed\'.'); return false; }, authFail: { route: 'all', params: { } } });

route

object route ( hashUrl, params = { }, attachDetector = true )

Navigate to the hash-based route according to a previously defined route. May specify route parameters as an object. Returns the component that was routed to.

By default, the Sling change detector is attached for the mounted component. Setting attachDetector to false prevents the change detector from being attached to this component.

Example route call:

route('user/5'); // Activates component at root for route 'user/:userId'

getRoute

void getRoute ( )

Get the current hash-based route.

getRouteSegments

string[] getRouteSegments ( )

Returns the current hash-based route's segments or an empty array if there are none.

Example:

console.log(getRouteSegments()); // [ 'user', '5' ]

Using Sling Reactive, route changes may be listened to by using a Sling Observable. Every time the route changes, the subscribed function below will be called.

let routeObservable = Observable(getRouteSegments());
routeObservable.subscribe(function(routeArr) {
    if (routeArr.length > 0) {
        this.primaryRoute = routeArr[0];
    }
    else {
        this.primaryRoute = '';
    }
}.bind(this));

getRouteParams

object getRouteParams ( )

Returns the current route's parameters as an object. Returns { } if there are none.

Core Change Detection API

setDetectionStrategy

void setDetectionStrategy ( newDetectionStrategy )

Set the new change detection strategy.

detectChanges

void detectChanges ( )

Trigger automatic change detection immediately.

isDetectorAttached

boolean isDetectorAttached ( eleId )

Returns true if Sling change detector is attached for the given element ID eleId.

detachDetector

void detachDetector ( eleId )

Detach the Sling change detector for the given element ID eleId.

XHR API

slRequest

Promise slRequest ( url, methodString, optionsObject = { } )

Create a XML HTTP Request (XHR) for the specified URL using the specified method, such as GET. Returns a Promise.

Request Option Default Detail
contentType application/json Set Content-Type request header.
body '' Body of the request.
withCredentials false Send cookies to 3rd party domains.
timeout 0 0 is no timeout. Specified in milliseconds.
headers {} Key/value request headers to set.

On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

slRequestWithBody

Promise slRequestWithBody ( url, methodString, bodyObject = { } )

Create a XML HTTP Request (XHR) for the specified URL using the specified method, such as GET, with the specified body object. Returns a Promise.

On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

slGet

Promise slGet ( url, data = { } )

Create a GET XHR request with the specified data which returns a Promise.

On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

slPost

Promise slPost ( url, data = { } )

Create a POST XHR request with the specified data which returns a Promise.

On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

slPut

Promise slPut ( url, data = { } )

Create a PUT XHR request with the specified data which returns a Promise. On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

slPatch

Promise slPatch ( url, data = { } )

Create a PATCH XHR request with the specified data which returns a Promise.

On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

slDelete

Promise slDelete ( url, data = { } )

Create a DELETE XHR request with the specified data which returns a Promise.

On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

Reactive API

Stream

object Stream( )

Returns a Sling stream. A stream is a sequence of values over time and the associated operations which are automatically applied as those values change.

Example stream usage using Sling XHR API:

slGet('https://jsonplaceholder.typicode.com/posts').then(xhrResp => {
    let postArr = JSON.parse(xhrResp.response);
    let postStream = Stream().from(postArr).transform(function(arr) {
        return arr.filter(v => v.userId === 1);
    }).transform(function(arr) {
        return arr.filter(v => v.body.includes('quo'));
    });
});

Equivalent stream usage using preexisting stream object and Sling XHR API:

let postStream2 = Stream();
postStream2.transform(function(arr) {
    return arr.filter(v => v.userId === 1);
}).transform(function(arr) {
    return arr.filter(v => v.body.includes('quo'));
});

slGet('https://jsonplaceholder.typicode.com/posts').then(xhrResp => {
    let postArr = JSON.parse(xhrResp.response);
    postArr.forEach(post => {
        postStream2.push(post);
    });
});

Stream Functions

push

object push( value )

Push a value onto a stream. All transformers automatically called. Transformers are only applied on new data. Returns the stream.

transform

object transform ( function(arrayData) { } )

Add a new transformer to stream. Is automatically applied to all existing and new data. Returns the stream.

subscribe

object subscribe( function(arrayData) { } )

Add a function that is automatically called when the underlying stream data changes. Returns the stream.

clearSubscription

object clearSubscription( functionToClear )

Remove functionToClear from the list of subscribed functions. Returns the stream.

clearSubscriptions

object clearSubscriptions( )

Remove all subscribed functions. Returns the stream.

call

object call ( function(arrayData) { } )

Call a function which operates on the stream's data. Returns the stream.

getData

[ ] getData( )

Returns a copy of stream array data.

clearTransformers

object clearTransformers( )

Clears all transformers acting on the stream. Data will remain in state of last transformation. Returns the stream.

from

object from ( newArray )

Set stream data to newArray and apply all existing transformers. Returns the stream.

Observable

object observable( array )

Returns a Sling observable. An observable is an array which may be listened to.

Example observable usage:

let myArray = [1, 2, 3];
let myObservable = Observable(myArray);
myObservable.subscribe(function(arr) {
    console.log('New length: ' + arr.length);
});

myObservable.getData().push(4);
obs.getData()[myObservable.getData().length] = 5;

Observable Functions

subscribe

void subscribe ( listenerFunction )

Listener function will be automatically called whenever the underlying array data changes. Returns the observable.

clearSubscription

object clearSubscription( functionToClear )

Remove functionToClear from the list of subscribed functions. Returns the observable.

clearSubscriptions

object clearSubscriptions( )

Remove all subscribed functions. Returns the observable.

getData

[ ] getData( )

Get the underlying array data.

BehaviorSubject

object BehaviorSubject( value )

Returns a Sling behavior subject. A behavior subject is a value that emits changes to subscribers.

Example behavior subject usage:

let subject = BehaviorSubject(5);
subject.next(subject.getData() + 1);
let value = subject.getData(); // 6

subject.subscribe(function (value) { console.log('Value: ' + value); });

BehaviorSubject Functions

subscribe

void subscribe ( listenerFunction )

Listener function will be automatically called whenever the subject's value changes. Returns the behavior subject.

clearSubscription

object clearSubscription( functionToClear )

Remove functionToClear from the list of subscribed functions. Returns the behavior subject.

clearSubscriptions

object clearSubscriptions( )

Remove all subscribed functions. Returns the behavior subject.

next

object next( value )

Set the next value of the subject. All subscribers are automatically called. Returns the behavior subject.

getData

primitive|object getData( )

Get the underlying value.