useless
v0.1.355
Published
Use Less. Do More. JavaScript on steroids.
Downloads
871
Readme
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.
> npm install useless
Browser builds
- useless.client.js — everything, except devtools
- useless.devtools.js — logging / stacktraces / assertions / Panic.js
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
, usingserver/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 towebpack-dev-server
incompatilibity with Webpack 2. This is solved by introducingwebpack-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 inuseless.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).
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):
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:
Following are $traits defined at useless/server
:
api.js
URL routingargs.js
command line arguments parsingconfig.js
handlesthis.config
management viaconfig.json
/ command line argumentsexceptions.js
a humane exception printer (replaces Node's default)http.js
powerful HTTP server abstractionipc.js
for app logic splitting between supervisor and supervised processes (RPC for app methods)pidfile.js
generates PID file upon startup / removes it on exitREPL.js
REPL-style debugger (experimental)source.js
remote access to app's own sourcesstdin.js
handling interactive terminal inputsupervisor.js
auto-restart on source code changetemplating.js
Underscore's templates + cachingtests.js
startup smoke tests for app traitsthumbnailer.js
inline thumbnailer for imagesuploads.js
image uploads basicsuptime.js
uptime trackingwebpack.js
full-featured WebPack integrationwebsocket.js
WebSocket basics
Macro processor for prototype definitions
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
- 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
_.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
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
- 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
- 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..
- jQuery+ — a pack of handy jQuery plugins
- Node+ — a custom lightweight alternative to jQuery
- DOMReference — traits for writing DOM-rendered components
- DSL for writing regexps in JS + named subexpressions