Created
June 30, 2016 05:24
-
-
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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