Skip to content

Instantly share code, notes, and snippets.

@dead-claudia
Created June 30, 2016 05:24
Show Gist options
  • Save dead-claudia/f6830ecfdbca9cc03288bbfd57f32472 to your computer and use it in GitHub Desktop.
Save dead-claudia/f6830ecfdbca9cc03288bbfd57f32472 to your computer and use it in GitHub Desktop.
Make Mithril 0.2.x controllers not require `new` (for ES6 support).
/* This is licensed under CC0. */
/**
* Notes:
* 1. This is fully ES3 compatible and works with the ES5 sham, but it leverages
* ES6's `WeakMap` or `Map` where possible.
* 2. This proxies `m`'s properties to Mithril's constructor where possible (in
* modern browsers, it always does). If you use the ES5 shams of
* `Object.defineProperty`, `Object.getOwnPropertyNames`, or
* `Object.getOwnPropertyDescriptor`, it should still properly proxy changes
* to `m` if the browser can support it in some fashion.
*/
m = (function (global, old, undefined) {
"use strict";
// In case it gets mutated on the target.
var apply = wrap.apply;
var coerce, unset;
function wrap(C) {
return {
// This ensures the controller is called, not constructed
controller: function () {
return apply.call(C.controller, this, arguments);
},
// This ensures the view is called on the correct instance
view: function () {
return apply.call(C.view, C, arguments);
},
}
}
// Helper to ignore errors
function attempt(f, arg) {
try { return f(arg); } catch (_) {}
}
// If the native `WeakMap` or `Map` exists, use it. Constant-time testing
// and setting are much faster, and a large number of components are very
// likely. Prefer `WeakMap` to ease up memory leaks, though.
if (global.WeakMap || global.Map) {
var cache = new (global.WeakMap || global.Map)();
coerce = function (C) {
var res = cache.get(C);
if (res == undefined) cache.set(C, res = wrap(C));
return res;
};
unset = function (C) {
cache.delete(C);
};
} else {
// Use a pair of arrays and memoize the last call and last 10 calls, so
// that commonly used elements are optimized. Large numbers of
// components are common, but most components aren't frequently used.
var components = [];
var replacements = [];
var lruArgs = [
undefined, undefined, undefined, undefined, undefined, undefined,
undefined, undefined, undefined, undefined,
];
var lruIndices = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
var lastIndex = -1;
var lruLen = 0;
var len = 0;
var init = 0;
var lastItem;
var rotate = function (index, C) {
// It's iterated in reverse to make the modifications more linear. The
// algorithm is a little cleaner, and it's quicker in some browsers.
for (var i = init !== -1 ? init : 9 /* 10-1 */; i > 0; i--) {
lruIndices[i - 1] = lruIndices[i]
lruArgs[i - 1] = lruArgs[i]
}
lruIndices[0] = index;
lruArgs[0] = C;
};
var getLru = function (C) {
var index = lruArgs.indexOf(C);
return index !== -1 ? lruIndices[index] : components.indexOf(C);
};
coerce = function (C) {
if (C === lastItem) return replacements[lastIndex];
var index = getLru(lastItem = C);
rotate(index, C);
if (index === -1) {
index = ++len;
components[index] = C;
replacements[index] = wrap(C);
}
lastIndex = index;
return C;
};
unset = function (C) {
// Easy case up front.
if (C === lastItem) {
lastItem = undefined;
lastIndex = -1;
}
var index = getLru(C);
if (index !== -1) {
// If the component existed, remove it and update all the
// indices, recifying the LRU cache in the process.
if (index < lastIndex) lastIndex--;
// Only rotate if it was recently used.
if (init !== -1) rotate(-1, undefined);
for (var i = 0; i < lruIndices.length; i++) {
if (index < lruIndices[i]) lruIndices[i]--
}
len--;
components.splice(index, 1);
replacements.splice(index, 1);
}
};
}
function m(type, arg1, arg2, arg3, arg4) {
// Let Mithril handle anything not a component with controller.
if (type == undefined || typeof type !== "object" ||
typeof type.controller !== "function") {
return apply.call(old, undefined, arguments);
}
type = coerce(type);
// Avoid an arguments allocation if possible.
switch (arguments.length) {
// 0 argument case handled in `type == undefined` check above.
case 1: return old(type);
case 2: return old(type, arg1);
case 3: return old(type, arg1, arg2);
case 4: return old(type, arg1, arg2, arg3);
case 5: return old(type, arg1, arg2, arg3, arg4);
default:
for (var args = [], i = 1; i < arguments.length; i++) {
args.push(arguments[i++]);
}
return apply.call(old, undefined, args);
}
}
if (attempt(function (test, a) {
Object.defineProperty(test, "a", {
"get": function () { return a; },
"set": function (v) { a = v; },
});
return test.a == undefined && (test.a = 2, a) == 2;
}, {a: 0}) {
var props = Object.getOwnPropertyNames(old);
for (var i = 0; i < props.length; i++) {
// Suppress errors for built-in, own non-configurable properties
// like Function.prototype.length in ES5 or user-defined
// non-configurable Object.prototype properties like Should.js's
// `Object.prototype.should`.
attempt(function (prop, desc, value) {
// Treat this as a proxy.
desc = Object.getOwnPropertyDescriptor(old, prop);
value = desc.value;
delete desc.value;
desc["get"] = function () { return value; };
desc["set"] = function (v) { value = old[prop] = v; };
Object.defineProperty(m, prop, desc);
}, props[i]);
}
} else {
// ES3 fallback, doesn't wrap getters and setters
for (var i in old) if ({}.hasOwnProperty.call(old, i)) {
attempt(function () { m[i] = old[i]; })
}
}
m.uncache = unset;
return m.component = m;
})(this, m);
/* This is licensed under CC0. */
/**
* Notes:
* 1. This requires a working ES6 implementation, and it relies on ES5 globals
* and ES6's WeakMap. It can be run through Babel, as long as a WeakMap is
* provided at runtime.
* 2. This proxies `m`'s properties to Mithril's constructor where possible.
*/
import m from "mithril";
export * from "mithril";
function wrap(C) {
return {
// This ensures the controller is called, not constructed
controller: function (...args) {
return C.controller.call(this, ...args);
},
// This ensures the view is called on the correct instance
view: (...args) => C.view(...args),
}
}
// Constant-time testing and setting are much faster, and a large number of
// components are very likely. `WeakMap` is used to avoid memory leaks.
const cache = new WeakMap();
function coerce(C) {
let res = cache.get(C);
if (res == undefined) cache.set(C, res = wrap(C));
return res;
}
export default function m(type, ...args) {
// Let Mithril handle anything not a component with controller.
if (type != undefined && typeof type === "object" &&
typeof type.controller === "function") {
type = coerce(type);
}
return old(type, ...args);
}
export {m as component};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment