Last active
July 28, 2017 03:45
-
-
Save wearhere/0e1b6dff58191c8e2c0a545ff5477a76 to your computer and use it in GitHub Desktop.
Draft of module for adding custom helpers as well as a bind operator that caches. Solution for https://github.com/tc39/proposal-bind-operator/issues/46.
This file contains 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
var f = ctx::ns.obj.func; | |
var g = ::ns.obj.func; | |
var h = new X::y; |
This file contains 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
import template from 'babel-template'; | |
const programCache = new WeakMap(); | |
/** | |
* Adds a custom helper function to the program. | |
* | |
* @param {Path} path - The AST path at which you want to use the helper. | |
* @param {String} name - The name of the helper. This is primarily useful for | |
* reading the transpiled output. It does not need to be globally unique | |
* --this function will create a new unique identifier from this name. | |
* @param {String} body - The body of the helper. Put "HELPER" wherever you | |
* reference the function by name. | |
* @return {Identifier} The identifier of the helper. This is unique per | |
* <program, name, body> tuple. | |
* | |
* @throws {Error} If a function with a different body is already registered for `name`. | |
*/ | |
export default function(path, name, body) { | |
if (!body.includes('HELPER')) { | |
throw new Error('Cannot register helper ("HELPER" token is missing).'); | |
} | |
const program = path.scope.getProgramParent(); | |
let names = programCache.get(program); | |
if (!names) programCache.set(program, names = new Map()); | |
let helpers = names.get(name); | |
if (!helpers) names.set(name, helpers = new Map()); | |
let id = helpers.get(body); | |
if (!id) { | |
id = program.path.scope.generateUidIdentifier(name); | |
const fn = template(body)({ HELPER: id }); | |
program.path.unshiftContainer('body', fn); | |
helpers.set(body, id); | |
} | |
return id; | |
} |
This file contains 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
import addCustomHelper from './add-custom-helper'; | |
export default function({ types: t }) { | |
function getTempId(scope) { | |
let id = scope.path.getData('functionBind'); | |
if (id) return id; | |
id = scope.generateDeclaredUidIdentifier('context'); | |
return scope.path.setData('functionBind', id); | |
} | |
function getStaticContext(bind, scope) { | |
const object = bind.object || bind.callee.object; | |
return scope.isStatic(object) && object; | |
} | |
function inferBindContext(bind, scope) { | |
const staticContext = getStaticContext(bind, scope); | |
if (staticContext) return staticContext; | |
const tempId = getTempId(scope); | |
if (bind.object) { | |
bind.callee = t.sequenceExpression([ | |
t.assignmentExpression('=', tempId, bind.object), | |
bind.callee | |
]); | |
} else { | |
bind.callee.object = t.assignmentExpression('=', tempId, bind.callee.object); | |
} | |
return tempId; | |
} | |
const helperTemplate = ` | |
const HELPER = (function() { | |
const cache = new WeakMap(); | |
return function(fn, obj) { | |
let fns = cache.get(obj); | |
if (!fns) cache.set(obj, fns = new WeakMap()); | |
let bound = fns.get(fn); | |
if (!bound) fns.set(fn, bound = fn.bind(obj)); | |
return bound; | |
} | |
})(); | |
`; | |
return { | |
inherits: require('babel-plugin-syntax-function-bind'), | |
visitor: { | |
CallExpression({ node, scope }) { | |
const bind = node.callee; | |
if (!t.isBindExpression(bind)) return; | |
const context = inferBindContext(bind, scope); | |
node.callee = t.memberExpression(bind.callee, t.identifier('call')); | |
node.arguments.unshift(context); | |
}, | |
BindExpression(path) { | |
const { node, scope } = path; | |
const context = inferBindContext(node, scope); | |
const helperId = addCustomHelper(path, 'bindCache', helperTemplate); | |
path.replaceWith(t.callExpression(helperId, [node.callee, context])); | |
} | |
} | |
}; | |
} |
This file contains 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
const lib = { | |
foo() {} | |
}; | |
assert.equal(::lib.foo, ::lib.foo); |
This file contains 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
var _context; | |
const _bindCache = function () { | |
const cache = new WeakMap(); | |
return function (fn, obj) { | |
let fns = cache.get(obj); | |
if (!fns) cache.set(obj, fns = new WeakMap()); | |
let bound = fns.get(fn); | |
if (!bound) fns.set(fn, bound = fn.bind(obj)); | |
return bound; | |
}; | |
}(); | |
var f = _bindCache((_context = ctx, ns.obj.func), _context); | |
var g = _bindCache((_context = ns.obj).func, _context); | |
var h = _bindCache((_context = new X(), y), _context); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
add-custom-helper.js
is modelled after https://github.com/github/babel-plugin-transform-custom-element-classes/blob/9216cb84612e575bb3fea20209eb9e3ea50c1b65/lib/index.js#L11. I've had difficulty finding documentation for Babel's AST APIs (https://github.com/thejameskyle/babel-handbook shows examples, but only of a subset of APIs, and doesn't really document parameters anyhow) so I'm not sure if I'm using the right APIs. Seems there might be a few ways to do things too 🙂.babel-plugin-transform-bind-cache.js
is forked from https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-function-bind, with my cache implementation roughly modelled after tc39/proposal-bind-operator#46 (comment).It successfully transpiles
actual.js
intoexpected.js
and passes the assert inexec.js
, as well as passes all the tests from https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-function-bind (updated for the new implementation), so as far as I'm concerned this is done, and am totally glad to put this up in a PR against that module or actually publish this, just wanted to get some feedback first—it's my first Babel plugin. :)