node package manager
Easy sharing. Manage teams and permissions with one click. Create a free org »

useless

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),  // x² 
    '/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..