CircleCI codecov Maintainability Gem Version

Jsrb

Jsrb is a template engine to generate JavaScript code in simple Ruby DSL.

Getting Started

Jsrb handler works in .jsrb view files. All ruby syntax is available and jsrb is provided in it. You can construct JavaScript code via jsrb.

name = jsrb.var!(:name) { 'foo' }
ary = jsrb.var! :ary
obj = jsrb.var! :obj
result = jsrb.var!
# var name = 'foo';
# var ary;
# var obj;
# var _v1; <- auto generate variable name

ary.set!([1, 2, 3])
# ary = [1, 2, 3];

obj.set!(
  name: name,
  profile: {
    age: 20,
    sex: 'm'
  }
)
# obj = {
#   name: name,
#   profile: {
#     age: 20,
#     sex: 'm'
#   }
# };

result.set! (obj.name + "(" + obj.profile.age + ")")
# _v1 = obj.name + "(" + obj.profile.age + ")";

ary.set! ary.map { |x| x * 2 }
# ary = ary.map(function(x) {
#  return x * 2;
# });

jsrb.if!(ary[1] === 4) {
  result.set! 'four'
}.elsif(ary[1] === 2) {
  result.set! 'two'
}.else {
  result.set! 'other'
}
# // the actual output doesn't looks like this, but will be better code in regard to variable scope.
# if (ary[1] === 4) {
#   _v1 = 'four'
# } else if (ary[1] === 2) {
#   _v1 = 'two'
# } else {
#   _v1 = 'other'
# }

result.set! jsrb.expr.Date.new!
# _v1 = new Date;

jsrb.expr.console.log('hello').as_statement!
# console.log('hello');

Usage

Statements

Variable declaration

jsrb.var! pushes a VariableDeclaration into current context.

# with variable name and initializer
jsrb.var!('varname') { 100 }
# var varname = 100;

# without initializer
jsrb.var!('varname')
# var varname;

# variable name is auto-generated if not specified
jsrb.var!
# var _v1;

# var! returns Jsrb::ExprChain instance, so that you can
# assign value with `.set!` method.
a = jsrb.var!
a.set! 100
# var _v1;
# v1 = 100;

If statement, and conditional expression

jsrb.if! pushes an IfStatement into current context, jsrb.if to build a conditional expression.

# start with `#if!`
# and chain `#elsif` to add next case.
# Note that this is a statement, not expression.
jsrb.if!(v === 1) {
  # ..
}.elsif(v === 2) {
  # ..
}.else {
  # ..
}

# if you don't need else clause, close with `#end`
jsrb.if!(v === 1) {
  # ..
}.end

# if you want to regard this as an expression, use `#if` without exclamation.
v.set! jsrb.if(v === 1) {
  # ..
}.else {
  # ..
}

Assignment statement

ExprChain#set! pushes an assignment statement (ExpressionStatement of AssignmentExpression).

a = jsrb.var! :a
a.set! 100
# var a;
# a = 100;

Expression statement

ExprChain#as_statement! pushes an ExpressionStatement of the left hand side of chain.

get_elements = jsrb.expr['getElements']
get_elements.('.foo').forEach { |n| n.delete.() }.as_statement!
# getElements('.foo').forEach(function(n) { return n.delete(); });

Expression chain

Expression chain (ExprChain class) is an utility class to construct JavaScript expressions.

Initialize with wrapping a ruby value

jsrb.expr create a new ExprChain instance, taking an initial value optionally. Some methods in jsrb automatically convert ruby expression to ExprChain.

x = jsrb.var! :x

x.set! jsrb.expr(100)
# x = 100;
# set! automatically wrap with ExprChain.
x.set! 100
# x = 100;
# If you need to compare by operator with another ExprChain,
# you have to wrap first.
x.set!(jsrb.expr(100) < jsrb.expr.y)
# x.set!(100 < jsrb.expr.y) will fail.

Chains

Member expression

ExprChain#[], #member!, #.. constructs MemberExpression. #[] and #member! is safe. #.. can be used only if the name has no conflict.

x = jsrb.var! :x

obj = jsrb.expr['someObj']
# jsrb.expr with no argument constructs empty chain,
# in which every member chain will be an identifier.
x.set! obj.field
# x = someObj['field'];
x.set! obj['field']
# x = someObj['field'];
x.set! obj.member! 'field'
# x = someObj['field'];

x.set! obj.send # NOTE that this is interpreted as a ruby Object's method, and causes an error.

Function Call

ExprChain#call, so #.(), #.. with argument or block constructs CallExpression.

x = jsrb.var! :x
console = jsrb.expr['console']

# using call method
console.log.('foo').as_statement!
# console.log('foo')
console.log.call('foo').as_statement!
# console.log('foo')

# using dynamic method
# if #..() has at least one argument or block, it will be a call expression.
console.log('foo').as_statement!
# console.log('foo')
x.map { |item| item.field }.as_statement!
# x.map(function(item) { return item.field; });

Operators

Any ruby-overridable and JS-existing operators are overridden for chaining. Supported operators are: ** + - * / % >> << & ^ | <= < > >= == === != ! && ||.

x = jsrb.var! :x
a = jsrb.expr['a']

x.set!(a === 1)
# x = a === 1;

x.set!(a * a)
# x = a * a;

x.set!(3 * a) # raises an error because Fixnum does not accept ExprChain as RHS.

New

ExprChain#new! constructs NewExpression.

x = jsrb.var! :x

x.set! jsrb.expr['Date'].new!
# x = new Date;

Function expression

ExprChain#for_all! constructs FunctionExpression. You can also construct it from Proc directly or passing a block.

ary = jsrb.var! :ary, [1, 2, 3]

ary.map((jsrb.expr['x'] * 2).for_all!('x')).as_statement!
# ary.map(function(x) { return x * 2; });

ary.map { |x| x * 2 }.as_statement!
# ary.map(function(x) { return x * 2; });

ary.map(->(x) { x * 2 }).as_statement!
# ary.map(function(x) { return x * 2; });

Conversion

Every Ruby's generic value will be converted to Javascript value by following rule:

Ruby value JavaScript
finite 100 100
NaN Float::NAN NaN
+Infinity Float::INFINITY Number.POSITIVE_INFINITY
-Infinity Float::INFINITY Number.NEGATIVE_INFINITY
true true
false false
:foo "foo"
"foo" "foo"
nil null
[1, 2, 3] [1, 2, 3]
{ foo: 'bar' } { "foo": "bar" }
->(x, y, z) { x + y + z } function (x, y, z) { return x + y + z; }

Customize Chain

You can add custom chain methods in ExprChain via Jsrb::ExprChain.#add_custom_chain.

Jsrb::ExprChain.add_custom_chain('log_here', '__tap_log__')

jsrb.expr['foo']['bar'].log_here.as_statement!
# __tap_log__(foo['bar']);

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/effective-spa/jsrb.

License

The gem is available as open source under the terms of the MIT License.