Pragma
The Pragma filter provides line-level control over JavaScript output through special comments. This allows fine-grained customization of the transpilation on a per-line basis.
Pragmas are specified with a comment at the end of a line using the format:
# Pragma: <name>
Available Pragmas
?? (or nullish)
Forces the use of nullish coalescing (??) instead of logical or (||).
This is useful when you want to distinguish between null/undefined and
falsy values like 0, "", or false.
a ||= b # Pragma: ??
# => a ??= b
x = value || default # Pragma: ??
# => let x = value ?? default
Requirements: ES2020 for ??, ES2021 for ??=
When to use: jQuery/DOM APIs often return null or undefined but valid
values could be falsy (e.g., 0 for an index). Use this pragma when you need
nullish semantics.
|| (or logical)
Forces the use of logical or (||) instead of nullish coalescing (??).
This is the inverse of the ?? pragma. It’s useful when you’re using the
or: :nullish or or: :auto options globally but need logical || behavior
for a specific line where the value could legitimately be false.
enabled ||= true # Pragma: logical
# => enabled ||= true (not ??=)
x = flag || default # Pragma: ||
# => let x = flag || default (not ??)
When to use: When a variable can hold false as a valid value and you
want the fallback to execute for false, not just null/undefined. For
example, boolean flags where false should trigger the default assignment.
function (or noes2015)
Forces traditional function syntax instead of arrow functions.
Arrow functions lexically bind this, which is often desirable. However,
DOM event handlers and jQuery callbacks typically need dynamic this binding
to reference the element that triggered the event.
element.on("click") { handle_click(this) } # Pragma: function
# => element.on("click", function() {handle_click(this)})
items.each { |item| process(item) } # Pragma: function
# => items.each(function(item) {process(item)})
Without the pragma:
items.each { |item| process(item) }
# => items.each(item => process(item))
When to use: jQuery event handlers, DOM callbacks, or any situation where
you need this to refer to the calling context rather than the lexical scope.
Alternative: You can also use Function.new { } (with the Functions
filter) to get the same result without a pragma:
fn = Function.new { |x| x * 2 }
# => let fn = function(x) {x * 2}
guard
Ensures splat arrays return an empty array when the source is null or
undefined.
In Ruby, [*nil] returns []. In JavaScript, spreading null throws an error.
This pragma guards against that by using nullish coalescing.
[*items] # Pragma: guard
# => items ?? []
[1, *items, 2] # Pragma: guard
# => [1, ...items ?? [], 2]
Requirements: ES2020 (for ??)
When to use: When working with data from external APIs or DOM methods that
might return null, and you want to safely spread the result into an array.
skip
Removes statements from the JavaScript output entirely. Works with:
requireandrequire_relativestatements- Method definitions (
def) - Class method definitions (
def self.method) - Alias declarations (
alias) - Block structures:
if/unless,begin,while/until,case
This is useful when a Ruby file contains code that shouldn’t be included in the JavaScript output (e.g., Ruby-specific methods, native Ruby gems, runtime dependencies that will be provided separately).
require 'prism' # Pragma: skip
# => (no output)
require_relative 'helper' # Pragma: skip
# => (no output)
def respond_to?(method) # Pragma: skip
# Ruby-only method, not needed in JS
true
end
# => (no output)
def self.===(other) # Pragma: skip
# Ruby-only class method
other.is_a?(Node)
end
# => (no output)
alias loc location # Pragma: skip
# => (no output)
unless defined?(RUBY2JS_SELFHOST) # Pragma: skip
require 'parser/current'
# Ruby-only code block
end
# => (no output - entire block removed)
require 'my_module' # No pragma, will be processed normally
# => import ... (if ESM filter is active)
When to use:
- When transpiling Ruby code that requires external dependencies provided separately in the JavaScript environment
- When using the
requirefilter and you need to exclude specific requires from bundling - When Ruby source files contain methods that are Ruby-specific and have no
JavaScript equivalent (e.g.,
respond_to?,is_a?,to_sexp) - When removing Ruby metaprogramming methods that don’t translate to JavaScript
Type Disambiguation Pragmas
Some Ruby methods have different JavaScript equivalents depending on the receiver type. These pragmas let you specify the intended type.
Note: Ruby2JS also supports automatic type inference from literals and constructor calls. Use these pragmas when the type cannot be inferred or when you need to override the inferred type.
array
Specifies that the receiver is an Array.
arr.dup # Pragma: array
# => arr.slice()
arr << item # Pragma: array
# => arr.push(item)
arr += [1, 2] # Pragma: array
# => arr.push(...[1, 2])
# Binary operators (Ruby array operations → JS equivalents)
x = a + b # Pragma: array
# => let x = [...a, ...b] (concatenation)
x = a - b # Pragma: array
# => let x = a.filter(x => !b.includes(x)) (difference)
x = a & b # Pragma: array
# => let x = a.filter(x => b.includes(x)) (intersection)
x = a | b # Pragma: array
# => let x = [...new Set([...a, ...b])] (union)
Note: All array operators work with type inference:
arr = []
arr += [1, 2] # No pragma needed - type inferred from []
# => let arr = []; arr.push(...[1, 2])
a = [1, 2, 3]
x = a - [2] # Type inferred from [1, 2, 3]
# => let a = [1, 2, 3]; let x = a.filter(x => ![2].includes(x))
When to use: When Ruby2JS can’t infer the type and you need array-specific behavior.
hash
Specifies that the receiver is a Hash (JavaScript object).
obj.dup # Pragma: hash
# => {...obj}
obj.include?(key) # Pragma: hash
# => key in obj
When to use: When you need hash-specific operations like the in operator
for key checking.
set
Specifies that the receiver is a Set (or Map).
s << item # Pragma: set
# => s.add(item)
s.include?(item) # Pragma: set
# => s.has(item)
s.delete(item) # Pragma: set
# => s.delete(item)
s.clear() # Pragma: set
# => s.clear()
When to use: When working with JavaScript Set or Map objects. By default:
<<becomes.push()(array behavior).include?becomes.includes()(array/string behavior).delete()becomesdelete obj[key](hash/object behavior).clear()becomes.length = 0(array behavior)
Use this pragma to get the correct Set methods: .add(), .has(),
.delete(), and .clear().
map
Specifies that the receiver is a JavaScript Map object.
m[key] # Pragma: map
# => m.get(key)
m[key] = value # Pragma: map
# => m.set(key, value)
m.key?(key) # Pragma: map
# => m.has(key)
m.delete(key) # Pragma: map
# => m.delete(key)
m.clear # Pragma: map
# => m.clear()
When to use: When working with JavaScript Map objects. By default:
hash[key]becomes bracket accesshash[key](object behavior)hash[key] = valuebecomeshash[key] = value(object behavior).key?()becomeskey in obj(object behavior).delete()becomesdelete obj[key](object behavior).clear()becomes.length = 0(array behavior)
Use this pragma to get the correct Map methods: .get(), .set(), .has(),
.delete(), and .clear().
string
Specifies that the receiver is a String.
str.dup # Pragma: string
# => str
Note: Strings in JavaScript are immutable, so .dup is a no-op.
Type Inference
Ruby2JS can automatically infer variable types from literals and constructor calls, reducing the need for explicit pragma annotations. When a variable is assigned a value with a recognizable type, subsequent operations on that variable will use the appropriate JavaScript equivalent.
Inferred Types
Types are inferred from:
Literals:
[]→ array{}→ hash""or''→ string- Integer/float literals → number
- Regular expressions → regexp
Constructor calls:
Set.new→ setMap.new→ mapArray.new→ arrayHash.new→ hashString.new→ string
Sorbet T.let annotations:
T.let(value, Array)→ arrayT.let(value, Hash)→ hashT.let(value, Set)→ setT.let(value, Map)→ mapT.let(value, String)→ stringT.let(value, T::Array[X])→ arrayT.let(value, T::Hash[K, V])→ hashT.let(value, T::Set[X])→ set
Examples
# Type inferred from literal - no pragma needed
items = []
items << "hello"
# => let items = []; items.push("hello")
# Type inferred from constructor
cache = Map.new
cache[:key] = value
# => let cache = new Map(); cache.set("key", value)
# Hash operations work automatically
config = {}
config.empty?
# => let config = {}; Object.keys(config).length === 0
Sorbet T.let
Ruby2JS recognizes Sorbet’s T.let type annotations.
The T.let wrapper is stripped from the output, and the type is used for
disambiguation:
# Sorbet annotation - T.let is stripped, type is used
items = T.let([], Array)
items << "hello"
# => let items = []; items.push("hello")
# Works with generic types too
cache = T.let({}, T::Hash[Symbol, String])
cache.empty?
# => let cache = {}; Object.keys(cache).length === 0
# Set types work correctly
visited = T.let(Set.new, T::Set[String])
visited << "page1"
# => let visited = new Set; visited.add("page1")
This allows you to write Ruby code that is both type-checked by Sorbet and correctly transpiled by Ruby2JS. The type annotations are used at compile time for disambiguation and removed from the JavaScript output.
Note: require 'sorbet-runtime' is automatically stripped from the output
since Sorbet is Ruby-only. This enables writing dual-target code that works
in both Ruby and JavaScript environments.
Instance Variables
Type inference works with instance variables, and types set in initialize are
tracked across all methods in the class:
class Counter
def initialize
@items = []
@visited = Set.new
end
def add(item)
@items << item # Uses push() - type known from initialize
@visited << item # Uses add() - type known from initialize
end
def unvisited
# Set.select auto-converts to [...set].filter()
@items.select { |x| !@visited.include?(x) }
end
end
Output:
class Counter {
constructor() {
this._items = [];
this._visited = new Set
}
add(item) {
this._items.push(item);
this._visited.add(item)
}
get unvisited() {
return this._items.filter(x => !this._visited.has(x))
}
}
Note: Instance variable types are only tracked when assigned in initialize.
Types assigned in other methods are not propagated class-wide.
Pragma Override
Explicit pragmas always take precedence over inferred types. This allows you to override the inference when needed:
items = [] # Inferred as array
items << x # Uses push()
items << y # Pragma: set # Pragma overrides - uses add()
Scope Boundaries
Local variables are scoped to their method. Types inferred in one method do not affect other methods:
def method_a
items = [] # Array in this scope
items << "a" # push()
end
def method_b
items = Set.new # Set in this scope
items << "b" # add()
end
Instance variables assigned in initialize are tracked class-wide (see
Instance Variables above). Instance variables assigned
in other methods are only tracked within that method.
When to Use Pragmas
You still need pragmas when:
- No assignment visible: The variable comes from a parameter or external source
- Type changes: A variable is reassigned to a different type
- Override needed: You want different behavior than the inferred type
def process(data) # data type unknown
data << item # Pragma: array # Pragma needed
end
items = get_items() # Return type unknown
items << x # Pragma: set # Pragma needed
Behavior Pragmas
These pragmas modify how specific Ruby patterns translate to JavaScript.
method
Converts .call() to direct invocation for function objects.
fn.call(x, y) # Pragma: method
# => fn(x, y)
When to use: When working with first-class functions stored in variables
that need to be invoked directly rather than using .call().
proto
Converts .class to .constructor for JavaScript prototype access.
obj.class # Pragma: proto
# => obj.constructor
When to use: When you need to access the JavaScript constructor function
rather than a literal .class property.
entries
Converts hash iteration to use Object.entries().
hash.each { |k, v| process(k, v) } # Pragma: entries
# => Object.entries(hash).forEach(([k, v]) => process(k, v))
When to use: When iterating over JavaScript objects where you need both
keys and values, and the standard .each translation doesn’t apply.
extend
Extends an existing JavaScript class (monkey patching) instead of defining a new class.
class String # Pragma: extend
def blank?
self.strip.empty?
end
end
# => String.prototype.blank = function() {return this.trim().length === 0}
class Array # Pragma: extend
def second
self[1]
end
end
# => Object.defineProperty(Array.prototype, "second", {
# enumerable: true, configurable: true,
# get() {return this[1]}
# })
When to use: When you need to add methods to built-in JavaScript classes
like String, Array, or Number, or extend classes defined elsewhere.
Since the pragma is a Ruby comment, it’s ignored when code runs in Ruby, making it ideal for dual-target development.
Usage Notes
Case Insensitivity
Pragma names are case-insensitive:
x = a || b # PRAGMA: ??
x = a || b # pragma: ??
x = a || b # Pragma: ??
# All produce: let x = a ?? b
Multiple Pragmas
You can use multiple pragmas on the same line, and they will all be applied:
# Both logical and method pragmas apply
x ||= fn.call(y) # Pragma: logical # Pragma: method
# => x ||= fn(y)
# Nullish and method together
x ||= fn.call(y) # Pragma: ?? # Pragma: method
# => x ??= fn(y)
You can also use different pragmas on different lines:
options ||= {} # Pragma: ??
element.on("click") { handle(this) } # Pragma: function
Filter Loading
The pragma filter is automatically loaded when you require it:
require 'ruby2js/filter/pragma'
Or specify it in your configuration:
Ruby2JS.convert(code, filters: [Ruby2JS::Filter::Pragma])
Combining with Other Filters
The pragma filter works alongside other filters. It automatically reorders
itself to run before the Functions and ESM filters, ensuring pragmas like
skip, entries, and method are processed correctly regardless of the
order filters are specified.
Background
The pragma filter was inspired by the need to handle edge cases in real-world JavaScript frameworks. When interfacing with existing JavaScript libraries, particularly jQuery and DOM APIs, Ruby2JS’s default output may not always produce the desired semantics.
Rather than changing global behavior, pragmas provide targeted control exactly where needed, keeping the rest of your code using standard Ruby2JS conventions.