angular's nicest part extracted as a standalone module for the browser and node.
It is meant to be very similar to expressions of angular, all changes will be documented.
bluerider exposes a .compile()
-method which can be used to compile evaluable expressions:
var expressions = require("bluerider");
evaluate = expressions.compile("1 + 1");
evaluate(); // returns 2
You can also set and get values on a given scope
:
evaluate = expressions.compile("name");
scope = { name: "Jenny" };
evaluate(scope); // returns 'Jenny'
evaluate = expressions.compile("ship.pirate.name = 'Störtebeker'");
evaluate(scope); // won't throw an error because angular's expressions are forgiving
console.log(scope.ship.pirate.name); // prints 'Störtebeker'
For assigning values, you can also use .assign()
:
evaluate = expressions.compile("ship.pirate.name");
evaluate.assign(scope, "Störtebeker");
console.log(scope.ship.pirate.name); // prints 'Störtebeker'
Check out their readme for further information.
Start webpack-dev-server with ./node_modules/.bin/webpack-dev-server --entry ./index.js
.
Then load the following url in your browser http://localhost:8080/
You can then access the compile method from the console to try it out.
Example :
var evaluate = compile("foo.bar == 'FooBar'");
evaluate({ foo: { bar: "FooBar" }});
Angular provides a mechanism to define filters on expressions:
expressions.filters.uppercase = input => input.toUpperCase();
expr = expressions.compile("'arr' | uppercase");
expr(); // returns 'ARR'
Arguments are evaluated against the scope:
expressions.filters.currency = (input, currency, digits) => {
input = input.toFixed(digits);
if (currency === "EUR") {
return input + "€";
} else {
return input + "$";
}
};
expr = expressions.compile("1.2345 | currency:selectedCurrency:2");
expr({
selectedCurrency: "EUR"
}); // returns '1.23€'
Compiles src
and returns a function evaluate()
. The compiled function is cached under compile.cache[src]
to speed up further calls.
Compiles also export the AST.
Example output of: compile("tmp + 1").ast
{ type: 'Program',
body:
[ { type: 'ExpressionStatement',
expression:
{ type: 'Identifier',
name: 'tmp',
constant: false,
toWatch: [ [Circular] ] } } ],
constant: false }
NOTE angular $parse do not export ast variable it's done by this library.
A cache containing all compiled functions. The src is used as key. Set this on false
to disable the cache.
An empty object where you may define your custom filters.
The internal Lexer.
The internal Parser.
Evaluates the compiled src
and returns the result of the expression. Property look-ups or assignments are executed on a given scope
.
Tries to assign the given value
to the result of the compiled expression on the given scope
and returns the result of the assignment.
There is no dist
build because it's not 2005 anymore. Use a module bundler like webpack or browserify. They're both capable of CommonJS and AMD.
Comment from angular.js/src/ng/parse.js
:
Angular expressions are generally considered safe because these expressions only have direct access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by obtaining a reference to native JS functions such as the Function constructor.
As an example, consider the following Angular expression:
{}.toString.constructor(alert("evil JS code"))
We want to prevent this type of access. For the sake of performance, during the lexing phase we disallow any "dotted" access to any member named "constructor".
For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor while evaluating the expression, which is a stronger but more expensive test. Since reflective calls are expensive anyway, this is not such a big deal compared to static dereferencing. This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits against the expression language, but not to prevent exploits that were enabled by exposing sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good practice and therefore we are not even trying to protect against interaction with an object explicitly exposed in this way.
A developer could foil the name check by aliasing the Function constructor under a different name on the scope.
In general, it is not possible to access a Window object from an angular expression unless a window or some DOM object that has a reference to window is published onto a Scope.
Kudos go entirely to the great angular.js team, it's their implementation!
Suggestions and bug-fixes are always appreciated. Don't hesitate to create an issue or pull-request. All contributed code should pass
- the tests in node.js by running
npm test
- the tests in all major browsers by running
npm run test-browser
and then visitinghttp://localhost:8080/bundle