The js package implements bindings to several popular JavaScript libraries for validating, reformatting, optimizing and analyzing JavaScript code. It builds on the V8 package (a fully standalone JavaScript engine in R) to call out to these libraries.
Several R packages allow the user to supply JavaScript code to be used as callback function or configuration object within a visualization or web application. By validating in R that the JavaScript code is syntactically correct and of the right type before actually inserting it in the HTML, we can avoid many annoying bugs.
The js_typeof
function simply calls the
typeof
operator on the given code. If the code is invalid,
a SyntaxError will be raised.
[1] "function"
Same for objects:
[1] "object"
Catch JavaScript typos:
Error: SyntaxError: Unexpected token '}'
A JavaScript program typically consists of script with a collection
of JavaScript statements. The js_validate_script
function
can be used to validate an entire script.
jscode <- readLines(system.file("js/uglify.min.js", package="js"), warn = FALSE)
js_validate_script(jscode)
[1] TRUE
Note that JavaScript does not allow for defining anonymous functions in the global scope:
[1] FALSE
To validate individual functions or objects, use the
js_typeof
function.
Esprima is a high performance, standard-compliant ECMAScript parser. It has full support for ECMAScript 2017 and returns a sensible syntax tree format as standardized by ESTree project.
type value
1 Keyword function
2 Identifier test
3 Punctuator (
4 Identifier x
5 Punctuator ,
6 Identifier y
7 Punctuator )
8 Punctuator {
9 Keyword var
10 Identifier z
11 Punctuator =
12 Identifier x
13 Punctuator *
14 Identifier y
15 Punctuator ;
16 Keyword return
17 Identifier z
18 Punctuator ;
19 Punctuator }
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "test"
},
"params": [
{
"type": "Identifier",
"name": "x"
},
{
"type": "Identifier",
"name": "y"
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "z"
},
"init": {
"type": "BinaryExpression",
"operator": "*",
"left": {
"type": "Identifier",
"name": "x"
},
"right": {
"type": "Identifier",
"name": "y"
}
}
}
],
"kind": "var"
},
{
"type": "ReturnStatement",
"argument": {
"type": "Identifier",
"name": "z"
}
}
]
},
"generator": false,
"expression": false,
"async": false
}
],
"sourceType": "script"
}
CoffeeScript is a little
language that compiles into JavaScript. It is an attempt to expose the
good parts of JavaScript in a simple way. The
coffee_compile
function binds to the coffee script
compiler.
(function() {
var square;
square = function(x) {
return x * x;
};
}).call(this);
var square;
square = function(x) {
return x * x;
};
The golden rule of CoffeeScript is: “It’s just JavaScript”. The code compiles one-to-one into the equivalent JS, and there is no interpretation at runtime. You can use any existing JavaScript library seamlessly from CoffeeScript (and vice-versa). The compiled output is readable and pretty-printed, will work in every JavaScript runtime, and tends to run as fast or faster than the equivalent handwritten JavaScript.
# Simple script
demo <- readLines(system.file("example/demo.coffee", package = "js"))
cat(demo, sep = "\n")
# Assignment:
number = 42
opposite = true
# Conditions:
number = -42 if opposite
# Functions:
square = (x) -> x * x
# Arrays:
list = [1, 2, 3, 4, 5]
# Objects:
math =
root: Math.sqrt
square: square
cube: (x) -> x * square x
# Splats:
race = (winner, runners...) ->
print winner, runners
# Existence:
alert "I knew it!" if elvis?
# Array comprehensions:
cubes = (math.cube num for num in list)
(function() {
var cubes, list, math, num, number, opposite, race, square,
slice = [].slice;
number = 42;
opposite = true;
if (opposite) {
number = -42;
}
square = function(x) {
return x * x;
};
list = [1, 2, 3, 4, 5];
math = {
root: Math.sqrt,
square: square,
cube: function(x) {
return x * square(x);
}
};
race = function() {
var runners, winner;
winner = arguments[0], runners = 2 <= arguments.length ? slice.call(arguments, 1) : [];
return print(winner, runners);
};
if (typeof elvis !== "undefined" && elvis !== null) {
alert("I knew it!");
}
cubes = (function() {
var i, len, results;
results = [];
for (i = 0, len = list.length; i < len; i++) {
num = list[i];
results.push(math.cube(num));
}
return results;
})();
}).call(this);
(function(){var cubes,list,math,num,number,opposite,race,square,slice=[].slice;number=42,opposite=!0,opposite&&(number=-42),square=function(x){return x*x},list=[1,2,3,4,5],math={root:Math.sqrt,square:square,cube:function(x){return x*square(x)}},race=function(){var runners,winner;return winner=arguments[0],runners=2<=arguments.length?slice.call(arguments,1):[],print(winner,runners)},"undefined"!=typeof elvis&&null!==elvis&&alert("I knew it!"),cubes=function(){var i,len,results;for(results=[],i=0,len=list.length;len>i;i++)num=list[i],results.push(math.cube(num));return results}()}).call(this);
One of the most popular and powerful libraries for working with JavaScript code is uglify-js. Uglify provides an extensive toolkit for manipulating the syntax tree of a piece of JavaScript code.
The uglify_reformat
binding parses a string with code
and then feeds it to the uglify code generator
which converts it back to JavaScript text, with custom formatting
options. This is nice for fixing whitespace, semicolons, etc.
code <- "function test(x, y){ x = x || 1; y = y || 1; return x*y;}"
cat(uglify_reformat(code, beautify = TRUE, indent_level = 2))
function test(x, y) {
x = x || 1;
y = y || 1;
return x * y;
}
The more impressive part of uglify-js is the compressor which
refactors the entire syntax tree, effectively rewriting your code into a
more compact but equivalent program. The uglify_optimize
function in R is a simple wrapper which parses code and then feeds it to
the compressor.
function test(x, y){ x = x || 1; y = y || 1; return x*y;}
function test(x,y){return x=x||1,y=y||1,x*y}
You can pass compressor options
to uglify_optimize
to control the various uglify
optimization techniques.
JSHint will automatically detect errors and potential problems in
JavaScript code. The jshint
function is R will return a
data frame where each row is a problem detected by the library (type,
line and reason of error):
id raw code evidence line character scope reason
1 (error) Missing semicolon. W033 var foo = 123 1 14 (main) Missing semicolon.
JSHint has many configuration options to control which types of code problems it will report on.