/scoped-function

ScopedFunction = new Function + scope

Primary LanguageJavaScriptMIT LicenseMIT

ScopedFunction = new Function + scope

ScopedFunction allows you to inject scope object into Function constructor. The properties of the scope object can be accesed in the function body as if they were closure variables: ScopedFunction('return s;', { s: 'hello' }) -> 'hello'.

Build paper-thin DSLs over JavaScript syntax — let your users write math in standard syntax, exp(10 * cos(x)), without prepending the nasty Math builtin, or pass libraries to in-browser JS playgrounds.

There's no runtime performance penalty for functions compiled using ScopedFunction. This library is a great foundation for safer and faster eval or with, the infamous optimization busters. ScopedFunction is tiny: <40 SLOC, or around 500 bytes minified.

Usage

// Use whichever you like
const ScopedFunction = require('ScopedFunction');
import ScopedFunction from 'scoped-function';
// You can also drop lib/scoped-function.js into your HTML if you feel like it

// Build your smallest DSL ever:
const trig = ScopedFunction('x', 'return sin(pi * x) + cos(pi * x)', {
  sin: Math.sin,
  cos: Math.cos,
  pi: Math.PI
});
const v = trig(1); // = -1

// Add slight rewriting:
const compileMath = e => ScopedFunction('x', `return (${e});`, Math);
compileMath('atan(exp(x) - 1)')(0); // = 0

// Inject libraries into user code
// Say you're building a JS course with live problems:
const userSolution = `
function ageMode(data) {
  if (_.isEmpty(data)) return undefined;
  return _.maxBy(
    _.values(_.groupBy(data, 'age')),
    v => v.length
  )[0].age;
}
`;
const _ = require('lodash');
// Note return + call: we can't parse the user-supplied function, but still inject the "_"
const userFn = ScopedFunction(`return (${userSolution});`, { _ })();
const isValid = userFn([{ age: 10 }, { age: 20 }, { age: 10 }]) === 10;

// You can also use ScopedFunction as a constructor:
const f = new ScopedFunction('x', 'return _.min(x)', { _ });

Standards Compliance and Usage Notes

ScopedFunction shallow-clones the scope object — you can't add, remove, or change the variables afterwards. However, the objects (as in { hub: {} }) are shared by reference. Use this to communicate between the functions, or opt out with a _.cloneDeep(...).

The functions produced mimic the ones that come from Function(...):

  • respond to typeof / instanceof
  • have proper call / bind / apply
  • accessing arguments still works
  • name is set to anonymous as per the spec.

If you find a deviation from the spec, drop an issue.

Both Function and ScopedFunction accept non-simple ES6 formal parameters: ...rest, { destructuring }, and default = true. Use these in the browser, but remember that your code is compiled as-is, not transpiled, and would break in browsers with poor ES6 support, like Safari.

Also note that this library does not give you a secure sanbox for untrusted code — use vm2 for that.

Installation

$ npm i --save scoped-function
$ yarn add scoped-function

Made by Vladimir Klepov.

License

MIT