Use Less. Do More. JavaScript on steroids.


Keywords
javascript, useless, framework, unit tests, test driven, OOP, AOP, CPS, DSL, metaprogramming, traits, mixins, singleton, functional, underscore, reflection, type matching, math, algorithms, exception handling, stack traces, logging, vector math, isomorphic, cross-platform, regular expressions, fullstack, nodejs
License
MIT
Install
npm install useless@0.1.355

Documentation

Use Less. Do More.

A research project, from which I learned a lot. Especially how one shouldn't write code and web frameworks in particular :)

In near future, it will be split into several loosely coupled, clean and maintainable NPM modules, for everyone's convenience. Stay tuned.

Installing | Wiki

> npm install useless

Browser builds

Upcoming features

  • Splitting of distinct framework parts to separate projects (finally, useful ones).

Recent updates / changelog

  • You can override config with command line args, e.g. node example webpack.offline=false, using server/config.js trait.

  • You can now handle interactive terminal input with server/stdin.js trait.

  • ololog logging facility is now separate project (redesigned from a scratch)

  • meta-fields facility now available as a separate NPM module.

  • webpack server trait now supports compress option for crunching scripts with Google Closure Compiler.

  • Webpack Hot Module Replacement now works (run the example app and try change example/index.css). It was previously broken due to webpack-dev-server incompatilibity with Webpack 2. This is solved by introducing webpack-hot-fix.js.

  • You can now use ES6 getter syntax for defining properties in components/traits: get something () { ... }

  • Working on webpack and babel integration for building front-end code (see the webpack server trait and the example app). Will feature continuous builds, hot module replacement, CSS imports and shared code extraction — all configurable from server's config.

  • More NPM modules to come: StackTracey and get-source.

  • ANSI color management now available as separate NPM module: ansicolor.

  • asTable function now available as a separate NPM module: as-table.

  • String.ify function now available as a separate NPM module: string.ify.

  • A wiki entry explaining the Stream concept. Thanks to @kroitor for the translation/adaptation from Russian!

  • Build system now utilizes webpack for squashing require imports. All external dependencies (e.g. underscore) now embedded in useless.client.js — no need to link them separately.

  • Component methods init / beforeInit / afterInit now support Promise interface for asynchronous initialization. Old callback-passing style is gone.

  • A wiki entry explaining the new __ namespace (brought by Promise+). It contains a pack of highly abstract data-processing algorithms that can handle any type of data (arrays, objects, scalars) and any kind of operator function (either sync or async).

  • An early alpha of the new HTTP server framework built around the recent Androgene subsystem. See a brief example here. It allows to write and debug complex asynchronous chains in no time, with unprecedented level of the error reporting legibility.

  • A working prototype of Androgene subsystem, delivering the "supervised Promises" concept for asynchronous/nonlinear logging and stack traces. It is also a core mechanism behind the upcoming unit test system (will replace the old Testosterone thing).

  • Read more...

Running example app

node example

If everything's ok, example app will be running at http://localhost:1333. Currently there's not much example code, but it's on the way.

You may want to look into these projects (built upon Useless.js):

  • Skychat — a simple WebRTC paint/chat app.
  • Wyg — a revolutionary WYSIWYG editor (demo).

Server app framework

require ('./useless')

UselessApp = $singleton (Component, {

    $defaults: {
        webpackEntries: {
            entry: {
                'shared': { // duplicate code will be extracted to shared.js
                    'useless.client':   "./node_modules/useless/build/useless.client.js",
                    'useless.devtools': "./node_modules/useless/build/useless.devtools.js",
                    'index':            "./example/index.js",
                }
            }
        },
        config: {
            webpack: {
                hotReload: true
            }
        }
    },

    $depends: [
        require ('./server/supervisor'), // for auto-reload on code change
        require ('./server/webpack'),
        require ('./server/http')
    ],

/*  Members starting with "/" are HTTP request handlers         */

    '/hello-world':       () => "Hello world!",          // text/plain; charset=utf-8
    '/hello-world/json':  () => ({ foo: 42, bar: 777 }), // application/json; charset=utf-8

/*  File serving  */

    '/':             () => $this.file ('./static/index.html'), // $this is a smart alias for `this`, accessible from anywhere in the request execution context
    '/static/:file': () => $this.file ('./static'),            // any file from ./static folder

/*  Query params matching  */

    '/sqr?x={\\d+}':          ({ x    }) => Math.pow (Number (x),         2),  //
    '/pow?x={\\d+}&n={\\d+}': ({ x, n }) => Math.pow (Number (x), Number (n)), // x^n
                 
/*  Put your JSONAPI stuff in /api    */

    '/api': { // tree-style definitions are supported
        
        'login':  { post: async () => $this.doLogin (await $this.receiveJSON) },
        'logout': { post:       () => $http.removeCookies (['email', 'password']) },
    },

/*  A complex request handler example, demonstrating some core features.

    All execution is wrapped into so-called "supervised Promise chain",
    so you don't need to pass the request context explicitly, it is always
    available as $http object in any promise callback related to request
    represented by that object.
    
    All thrown errors and all log messages are handled by the engine
    automagically. It builds a process/event hierarchy that reflects the
    actual execution flow, so you don't need to run a debugger to see what's
    going on, it's all in the log. Request is automatically ended when an
    unhandled exception occurs, no need to trigger it explicitly.               */

    async doLogin ({ email, password }) {

        if (await this.findUser ({ email, password })) {

            $http.setCookies ({ email, password })

        } else {

            throw new Error ('Wrong credentials')
        }
    },

    async findUser (criteria) { /* ... */ },

    init () {

        log.ok ('App started')
    }
})

Example report generated from a Promise chain:

Promise stack demo

Following are $traits defined at useless/server:

  • api.js URL routing
  • args.js command line arguments parsing
  • config.js handles this.config management via config.json / command line arguments
  • exceptions.js a humane exception printer (replaces Node's default)
  • http.js powerful HTTP server abstraction
  • ipc.js for app logic splitting between supervisor and supervised processes (RPC for app methods)
  • pidfile.js generates PID file upon startup / removes it on exit
  • REPL.js REPL-style debugger (experimental)
  • source.js remote access to app's own sources
  • stdin.js handling interactive terminal input
  • supervisor.js auto-restart on source code change
  • templating.js Underscore's templates + caching
  • tests.js startup smoke tests for app traits
  • thumbnailer.js inline thumbnailer for images
  • uploads.js image uploads basics
  • uptime.js uptime tracking
  • webpack.js full-featured WebPack integration
  • websocket.js WebSocket basics

Macro processor for prototype definitions

How-to & Examples

Vec2 = $prototype ({

    /*  Constructor
     */
    constructor: function (x, y) { this.x = x; this.y = y },

    /*  Instance method
     */
    add (other) { return new Vec2 (this.x + other.x, this.y + other.y) }

    /*  Instance property (.length)
     */
    get length () { return Math.sqrt (this.x * this.x + this.y * this.y) }),

    /*  Static property: Vec2.zero
     */
    zero: $static ($property (function () { return new Vec2 (0, 0) })),

    /*  Static method: Vec2.dot (a, b)
     */
    dot: $static (function (a, b) { return a.x * b.x + a.y * b.y }),

    /*  Tag groups for convenience
     */
    $static: {
        unit: $property (function () { return new Vec2 (1, 1) }),
        one:  $alias ('unit') }, // member aliases
})

/*  Inheritance (relies on native JavaScript prototype semantics)
 */
BetterVec2 = $extends (Vec2, { /* ... */ })

Component model

How-to & Examples

  • Binds own methods to this automatically
  • Manages bindable $trigger / $barrier / $observableProperty members
  • Tracks bound components / auto-unbinds upon deinitialization
  • Holds parent-child relationship / handles automatic deinitialization
  • Enables $traits to chain into method calls by overlapping method definitions
  • Enforces configuration contracts ($requires, $defaults)

Multicast model for method calls with simple functional I/O

How-to & Examples | Reference

  • _.trigger, _.triggerOnce / one-to-many broadcast
  • _.barrier / synchronization primitive
  • _.observable / state change notifications

Raw API (same for every mentioned primitive):

var mouseMoved = _.trigger ()

/*  Binding
 */
mouseMoved (function (x, y) { }) // bind
mouseMoved (someCallback)        // bind another
mouseMoved.once (someCallback)   // bind with 'once' semantics (auto-unbinds itself upon calling)

/*  Calling
 */
mouseMove (12, 33)               // call

/*  Unbinding
 */
mouseMove.off (someCallback)     // unbinds specific listener
mouseMove.off ()                 // unbinds everything
_.off (someCallback)             // unbinds callback from everything it's bound to

Using $component:

Compo = $component ({

    didLayout:     $trigger (),
    layoutReady:   $barrier (),             // it's like jQueryish $(document).ready
    value:         $observableProperty (),  // for property change notifications
    
    init: function () {
        doSomeUselessAsyncJob (function () {
           this.layoutReady () }) }, // signals that layout is ready

    doLayout: function () {
        this.didLayout () } })       // simply call to perform multicast
compo = new Compo ()

compo.didLayout (function () {
    /*  Gets called whenether layout has rebuilt */ })

compo.layoutReady (function () {
    /*  Postpones until DOM is ready.
        If already, calls immediately (like $(document).ready) */ })

compo.valueChange (function (value, oldValue) {
    /*  Gets called whenether property has assigned distinct value */ })

compo.value = 10 // simply assign a value to notify listeners
compo.value = 10 // won't trigger, as not changed

Bindable methods for ad-hoc code injection

Raw API:

_.onAfter   (Player.prototype, 'move', function (x, y) { /* this will execute after move calls */ })
_.onBefore  (Player.prototype, 'move', function (x, y) { /* this will execute before */ })
_.intercept (Player.prototype, 'move', function (x, y, originalMethod) {
    originalMethod.call (this, x, y) })

Using $component + 'once' semantics:

Button = $component ({
    layout: $bindable (function () { /* ... */ }) })
    
button = new Button ()
button.layout.onceBefore (function () { log ("I'm called before next layout()") })
button.layout ()
button.layout () // won't print anything

Math utility for front-end works

Reference

Working with ranges:

    _.lerp  (t, min, max)  // linear interpolation between min and max
    _.clamp (n, min, max)  // clips if out of range
    
    /*  Projects from one range to another (super useful in widgets implementation)
     */
    _.rescale (t, [fromMin, fromMax], [toMin, toMax], { clamp: true })

Vector math (Vec2, Transform, BBox, Bezier, intersections):

   var offsetVec = this.anchor.sub (this.center).normal.perp.scale (
                       Bezier.cubic1D (
                           Vec2.dot (direction.normal, upVector), 0, 1.22, 0, 1.9))
   var where = this.bodyBBox.nearestPointTo (this.anchor, this.borderRadius)
   domElement.css (BBox.fromPoints (pts).grow (20).offset (position.inverse).css)

Error handling

node.js stacktrace

Panic.js demo

  • Cross-platform uncaught exception handling (works around incomplete 'onerror' impl. in Safari).
  • Uncaught exceptions pass through network API calls
  • Client displays server's exceptions as if it was single environment
  • Complete API for it's internals
  • Strips third party calls (clean mode)
  • Fetches source code (local/remote)
  • Nice output
    • Console mode (replaces default Node.js exception printer)
    • GUI mode (a pop-up dialog with expandable source lines)

Test framework

How-to & Examples

assertion demo

assertion demo

  • Tests before code
  • Tests as documentantion
  • Rich library of assertions
  • Asynchronous / nested assertions
  • Intercepts global log, displaying it in running assertion context
  • Custom assertions
  • Humane error reporting
  • Browser-side support (see demo: youtube.com/watch?v=IWLE8omFnQw)

And more..