ES Levels

By default, Ruby2JS targets ES2020, which is the minimum supported version. ES2020 includes let/const, arrow functions, template literals, classes, spread syntax, optional chaining (?.), and nullish coalescing (??).

By passing an eslevel option to the convert method, you can target a newer ECMAScript version. Every newer level enables older levels, so for example 2021 will enable ES2020 features plus ES2021-specific features.

Baseline Features (ES2015-ES2019)

The following features are always available since ES2020 is the minimum supported version:

  • "#{a}" `${a}`
  • a = 1 let a = 1
  • A = 1 const A = 1
  • a, b = b, a [a, b] = [b, a]
  • a, (foo, *bar) = x let [a, [foo, ...bar]] = x
  • def f(a, (foo, *bar)) function f(a, [foo, ...bar])
  • def a(b=1) function a(b=1)
  • def a(*b) function a(...b)
  • .each_value for (i of ...) {}
  • a(*b) a(...b)
  • "#{a}" \`${a}\`
  • lambda {|x| x} (x) => {return x}
  • proc {|x| x} (x) => {x}
  • a {|x|} a((x) => {})
  • class Person; end class Person {}
  • Class.new do; end class {}
  • (0...a).to_a [...Array(a).keys()]
  • (0..a).to_a [...Array(a+1).keys()]
  • (b..a).to_a Array.from({length: (a-b+1)}, (_, idx) => idx+b)
  • hash => {a:, b:} let { a, b } = hash

Class support includes constructors, super, methods, class methods, instance methods, instance variables, class variables, getters, setters, attr_accessor, attr_reader, attr_writer, etc.

Additionally, the functions filter provides the following conversions:

  • Array(x) Array.from(x)
  • .inject(n) {} .reduce(() => {}, n)
  • a[0..2] = v a.splice(0, 3, ...v)
  • a ** b a ** b
  • .include? .includes
  • .values() Object.values()
  • .entries() Object.entries()
  • .each_pair {} for (let [key, value] of Object.entries()) {}
  • include M Object.defineProperties(..., Object.getOwnPropertyDescriptors(M))
  • .merge {...a, ...b}
  • .flatten .flat(Infinity)
  • .lstrip .trimEnd
  • .rstrip .trimStart
  • a.to_h Object.fromEntries(a)
  • Hash[a] Object.fromEntries(a)
  • a&.b a?.b
  • .scan Array.from(str.matchAll(/.../g), s => s.slice(1))
  • a.nil? ? b : a a ?? b

Async support:

  • async def async function
  • async lambda async =>
  • async proc async =>
  • async -> async =>
  • foo bar, async do...end foo(bar, async () => {})

Keyword arguments and optional keyword arguments are mapped to parameter destructuring. Rest arguments can be used with keyword arguments and optional keyword arguments. rescue without a variable maps to catch without a variable.

Classes defined with a method_missing method will emit a Proxy object for each instance that will forward calls. Note that in order to forward arguments, this proxy will return a function that will need to be called, making it impossible to proxy attributes/getters. As a special accommodation, if the method_missing method is defined to only accept a single parameter it will be called with only the method name, and it is free to return either values or functions.

ES2021 support

When option eslevel: 2021 is provided, the following additional conversions are made:

  • x ||= 1 x ||= 1
  • x &&= 1 x &&= 1
  • x = y if x.nil? x ??= y
  • 1000000.000001 1_000_000.000_001
  • .gsub .replaceAll

The x = y if x.nil? pattern provides an idiomatic Ruby way to express nullish assignment. This is useful when you want nullish semantics (only assign if nil) rather than the falsy semantics of ||= (which also triggers on false).

ES2022 support

  • @x this.#x (unless the underscored_private option is set to true)
  • @@x ClassName.#x
  • self.a = [] static a = [] (within a class)
  • private methods after private use # prefix (e.g., #helper())

Private method support allows you to use Ruby’s private keyword to mark methods as private:

class Calculator
  def calculate(x)
    helper(x)      # Calls this.#helper(x)
  end

  private

  def helper(x)    # Becomes #helper(x)
    x * 2
  end
end

Both implicit and explicit self calls to private methods are correctly prefixed:

helper(x)       # => this.#helper(x)
self.helper(x)  # => this.#helper(x)

When underscored_private: true is set, private methods use _ prefix instead of #.

When the functions filter is enabled, the following additional conversions are made:

  • x[-2] x.at(-2)
  • x.last x.at(-1)

ES2023 support

When option eslevel: 2023 is provided, the following additional conversion is made by the functions filter:

  • .sort_by {} .toSorted()

Ruby’s sort_by method uses ES2023’s non-mutating toSorted():

# Ruby
people.sort_by { |p| p.age }

# JavaScript (ES2023)
people.toSorted((p_a, p_b) => p_a.age < p_b.age ? -1 : p_a.age > p_b.age ? 1 : 0)

For older ES levels, sort_by uses slice().sort() to avoid mutating the original array.

ES2024 support

When option eslevel: 2024 is provided, the following additional conversion is made by the functions filter:

  • .group_by {} Object.groupBy()

Ruby’s group_by method maps directly to ES2024’s Object.groupBy():

# Ruby
people.group_by { |p| p.age }

# JavaScript (ES2024)
Object.groupBy(people, p => p.age)

For older ES levels, group_by uses a reduce() fallback to build the grouped object.

ES2025 support

When option eslevel: 2025 is provided, the following additional conversion is made by the functions filter:

  • Regexp.escape() RegExp.escape()

Ruby’s Regexp.escape method maps directly to ES2025’s RegExp.escape():

# Ruby
Regexp.escape("hello.world")

# JavaScript (ES2025)
RegExp.escape("hello.world")

Next: Cli