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 js
is provided in it. You can construct JavaScript code via js
.
name = js.var!(:name) { 'foo' }
ary = js.var! :ary
obj = js.var! :obj
result = js.var!
# var name = 'foo';
# var ary;
# var obj;
# var _v1; // auto generated
js.set! ary, [1, 2, 3]
# ary = [1, 2, 3];
js.set! obj, {
name: name,
profile: {
age: 20,
sex: 'm'
}
}
# obj = {
# name: name,
# profile: {
# age: 20,
# sex: 'm'
# }
# };
js.set! result, (obj.name + "(" + obj.profile.age + ")")
# _v1 = obj.name + "(" + obj.profile.age + ")";
js.set! ary, ary.map { |x| x * 2 }
# ary = ary.map(function(x) {
# return x * 2;
# });
js.if!(ary[1] === 4) {
js.set! result, 'four'
}.elsif(ary[1] === 2) {
js.set! result, 'two'
}.else {
js.set! result, 'other'
}
# // The actual output will have certain immediate functions
# // that preserve variable scope for each case.
# if (ary[1] === 4) {
# _v1 = 'four'
# } else if (ary[1] === 2) {
# _v1 = 'two'
# } else {
# _v1 = 'other'
# }
js.set! result, js.expr.Date.new
# _v1 = new Date;
js.set! js.expr.console.log('hello')
# console.log('hello');
Usage
In contrast to Ruby, statements and expressions are specifically distinguished as different elements in JavaScript. And the program is composed of a list of statements. This means that the jsrb file will have a series of statement pushing expression in it.
To make clear whether a method is pushing statement or not, jsrb adopted the rule that the name of method pushing statement should be #..!
.
Statements
Variable declaration
js.var!
pushes a VariableDeclaration into current context.
# with variable name and initializer
js.var!('varname') { 100 }
# var varname = 100;
# without initializer
js.var!('varname')
# var varname;
# variable name is auto-generated if not specified
js.var!
# var _v1;
# var! returns Jsrb::ExprChain instance, so that you can
# assign value with `.set!` method.
a = js.var!
js.set! a, 100
# var _v1;
# v1 = 100;
If statement, and conditional expression
js.if!
pushes an IfStatement into current context, js.if
to build a conditional expression.
# start with `#if!`
# and chain `#elsif` to add next case.
# Note that this is a statement, not expression.
js.if!(v === 1) {
# ..
}.elsif(v === 2) {
# ..
}.else {
# ..
}
# if you don't need else clause, close with `#end`
js.if!(v === 1) {
# ..
}.end
# if you want to regard this as an expression, use `#if` without exclamation.
js.set! v, js.if(v === 1) {
# ..
}.else {
# ..
}
Expression statement
js.do!
pushes an ExpressionStatement of a given expression.
get_elements = js.expr[:getElements]
js.do! get_elements.('.foo').forEach { |n| n.delete.() }
# getElements('.foo').forEach(function(n) { return n.delete(); });
Expressions
Expression chain (ExprChain
class) is an utility class to construct JavaScript expressions.
Initialize with wrapping ruby values
js.expr
create a new ExprChain
instance, taking an initial value optionally.
Some methods in js
automatically convert ruby value to ExprChain.
x = js.var! :x
js.set! x, js.expr(100)
# x = 100;
# set! automatically wraps argument with ExprChain.
js.set! x, 100
# x = 100;
#
# Note that if you need to compare a ruby value by operator with another one,
# you have to wrap it.
js.set! x, (js.expr(100) < js.expr.y) # (100 < js.expr.y) will fail.
See the conversion section to check mappings from ruby value to JavaScript one.
Chains
Member expression
ExprChain#[], #..
constructs MemberExpression.
#[]
and #member!
is safe. #..
can be used only if the name has no conflict.
x = js.var! :x
obj = js.expr[:someObj]
# js.expr with no argument constructs empty chain,
# in which every member chain will be an identifier.
js.set! x, obj.field
# x = someObj['field'];
js.set! x, obj[:field]
# x = someObj['field'];
js.set! x, obj.send # NOTE that this is interpreted as a ruby Object's method, and causes an error.
Assignment
ExprChain#set
constructs an AssignmentExpression.
a = js.var! :a
# var a;
js.do! a.set 100
# a = 100;
# js.set!(a, b) is short hand of js.do!(a.set(b))
js.set! a, 100
# a = 100;
Function Call
ExprChain#call, so #.(), #.. with argument or block
constructs CallExpression.
console = js.expr[:console]
# using call method
js.do! console.log.('foo')
# console.log('foo')
js.do! console.log.call('foo')
# console.log('foo')
# using dynamic method
# if #..() has at least one argument or block, it will be a call expression.
js.do! console.log('foo')
# console.log('foo')
js.do! js.expr(:ary).forEach { |item| item.execute.() }
# ary.forEach(function(item) { return item.execute(); });
Operators
Any ruby-overridable and JS-existing operators are overridden for chaining.
Supported operators are: ** + - * / % >> << & ^ | <= < > >= == === != ! && ||
.
x = js.var! :x
a = js.expr[:a]
js.set! x, (a === 1)
# x = a === 1;
js.set! x, (a * a)
# x = a * a;
js.set! x, (3 * a) # raises an error because the method :* of Fixnum does not accept ExprChain as RHS.
New
ExprChain#new
constructs NewExpression.
x = js.var! :x
js.set! x, js.expr[:Date].new
# x = new Date;
Function expression
ExprChain#forall
constructs FunctionExpression. You can also construct it directly from Proc or passing a block.
ary = js.var! :ary, [1, 2, 3]
js.do! ary.map((js.expr[:x] * 2).forall('x'))
# ary.map(function(x) { return x * 2; });
js.do! ary.map { |x| x * 2 }
# ary.map(function(x) { return x * 2; });
js.do! ary.map(->(x) { x * 2 })
# 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__')
js.do! js.expr[:foo][:bar].log_here
# __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.