Use Less. Do More. JavaScript on steroids.
A research project, from which I learned a lot. Especially how one shouldn't write code and web frameworks in particular :)
-THIS PROJECT IS UNMAINTAINED, SHOULDN'T BE USED / LEFT HERE JUST FOR THE EDUCATIONAL PURPOSES
Hey, as the name suggests, this is not something you're supposed to use! The project contains a number of interesting ideas (like traits system) which could be further explored and evolved as separate projects — but I probably won't do that in near future. Anyway, feel free to read the docs/code and use it for whatever you like.
> npm install useless
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).
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):
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
handles this.config
management via config.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 basicsVec2 = $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, { /* ... */ })
_.trigger
, _.triggerOnce
/ one-to-many broadcast_.barrier
/ synchronization primitive_.observable
/ state change notificationsRaw 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
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
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)