ruby2js 3.5.3 → 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -665
  3. data/lib/ruby2js.rb +60 -15
  4. data/lib/ruby2js/converter.rb +39 -3
  5. data/lib/ruby2js/converter/args.rb +6 -1
  6. data/lib/ruby2js/converter/assign.rb +159 -0
  7. data/lib/ruby2js/converter/begin.rb +7 -2
  8. data/lib/ruby2js/converter/case.rb +7 -2
  9. data/lib/ruby2js/converter/class.rb +80 -21
  10. data/lib/ruby2js/converter/class2.rb +107 -33
  11. data/lib/ruby2js/converter/def.rb +7 -3
  12. data/lib/ruby2js/converter/dstr.rb +8 -3
  13. data/lib/ruby2js/converter/for.rb +1 -1
  14. data/lib/ruby2js/converter/hash.rb +28 -6
  15. data/lib/ruby2js/converter/hide.rb +13 -0
  16. data/lib/ruby2js/converter/if.rb +10 -2
  17. data/lib/ruby2js/converter/import.rb +19 -4
  18. data/lib/ruby2js/converter/kwbegin.rb +9 -2
  19. data/lib/ruby2js/converter/literal.rb +14 -2
  20. data/lib/ruby2js/converter/logical.rb +1 -1
  21. data/lib/ruby2js/converter/module.rb +41 -4
  22. data/lib/ruby2js/converter/next.rb +10 -2
  23. data/lib/ruby2js/converter/opasgn.rb +8 -0
  24. data/lib/ruby2js/converter/redo.rb +14 -0
  25. data/lib/ruby2js/converter/return.rb +2 -1
  26. data/lib/ruby2js/converter/send.rb +73 -8
  27. data/lib/ruby2js/converter/vasgn.rb +5 -0
  28. data/lib/ruby2js/converter/while.rb +1 -1
  29. data/lib/ruby2js/converter/whilepost.rb +1 -1
  30. data/lib/ruby2js/converter/xstr.rb +2 -3
  31. data/lib/ruby2js/demo.rb +53 -0
  32. data/lib/ruby2js/es2022.rb +5 -0
  33. data/lib/ruby2js/es2022/strict.rb +3 -0
  34. data/lib/ruby2js/filter.rb +9 -1
  35. data/lib/ruby2js/filter/active_functions.rb +44 -0
  36. data/lib/ruby2js/filter/camelCase.rb +6 -3
  37. data/lib/ruby2js/filter/cjs.rb +2 -0
  38. data/lib/ruby2js/filter/esm.rb +118 -26
  39. data/lib/ruby2js/filter/functions.rb +137 -109
  40. data/lib/ruby2js/filter/{wunderbar.rb → jsx.rb} +29 -7
  41. data/lib/ruby2js/filter/node.rb +58 -14
  42. data/lib/ruby2js/filter/nokogiri.rb +12 -12
  43. data/lib/ruby2js/filter/react.rb +182 -57
  44. data/lib/ruby2js/filter/require.rb +102 -11
  45. data/lib/ruby2js/filter/return.rb +13 -1
  46. data/lib/ruby2js/filter/stimulus.rb +187 -0
  47. data/lib/ruby2js/jsx.rb +309 -0
  48. data/lib/ruby2js/namespace.rb +75 -0
  49. data/lib/ruby2js/serializer.rb +19 -12
  50. data/lib/ruby2js/sprockets.rb +40 -0
  51. data/lib/ruby2js/version.rb +3 -3
  52. data/ruby2js.gemspec +2 -2
  53. metadata +23 -13
  54. data/lib/ruby2js/filter/esm_migration.rb +0 -72
  55. data/lib/ruby2js/rails.rb +0 -63
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a0cc1907c261ee4de0bf29def31b92148ceb0b1247035db8553515106cc800b
4
- data.tar.gz: 8141a424cf50daa57e96aea4d2a601b283377a05769e455c0c8f4ffadd3690ae
3
+ metadata.gz: eada6454a1374751fa61b2522291a4f7c61023ec0218ba98d7fdcb19e0067f0e
4
+ data.tar.gz: 4bef375ce47cef8733f0e08250dfbf4f6968e9a043aa53825d9198db3fafdd7f
5
5
  SHA512:
6
- metadata.gz: 2836e4593b6f4fa15be750e8c83231e90c62d5bce75768119d4ba54b7b3459601018744eba95952db906d1e5ca64a40658d6449348239056cb9297647f424f88
7
- data.tar.gz: f009c9829447798d8b1a21fc2cf6e6033cb21c22999d73f7289de08d03edbbd1ce35b1a6f125693803758e779a779379cbd78ed291a9224e4b7cbb0a257ef1ad
6
+ metadata.gz: 511f8917dfac3baaa45f33147430d23ea0a41e3ed2b74147d9589e1385a6f07d36aab029f47ba05f07f974c3543a7823a7c943b190e30fee2a240ce604d25d87
7
+ data.tar.gz: 61c8b8b5f3fbbecde1fb1bc0d23a35c13202a21467009665faba3b596607272fc1b1601f4f0156cf9d60ed80ff1e96ffa5204a1fa83b57430b729c6bc9b9b9d1
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- Ruby2js
1
+ Ruby2JS
2
2
  =======
3
3
 
4
4
  Minimal yet extensible Ruby to JavaScript conversion.
@@ -7,62 +7,14 @@ Minimal yet extensible Ruby to JavaScript conversion.
7
7
  [![Gem Version](https://badge.fury.io/rb/ruby2js.svg)](https://badge.fury.io/rb/ruby2js)
8
8
  [![Gitter](https://badges.gitter.im/ruby2js/community.svg)](https://gitter.im/ruby2js/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
9
9
 
10
- Description
11
- ---
12
-
13
- The base package maps Ruby syntax to JavaScript semantics.
14
- For example:
15
-
16
- * a Ruby Hash literal becomes a JavaScript Object literal
17
- * Ruby symbols become JavaScript strings.
18
- * Ruby method calls become JavaScript function calls IF
19
- there are either one or more arguments passed OR
20
- parenthesis are used
21
- * otherwise Ruby method calls become JavaScript property accesses.
22
- * by default, methods and procs return `undefined`
23
- * splats mapped to spread syntax when ES2015 or later is selected, and
24
- to equivalents using `apply`, `concat`, `slice`, and `arguments` otherwise.
25
- * ruby string interpolation is expanded into string + operations
26
- * `and` and `or` become `&&` and `||`
27
- * `a ** b` becomes `Math.pow(a,b)`
28
- * `<< a` becomes `.push(a)`
29
- * `unless` becomes `if !`
30
- * `until` becomes `while !`
31
- * `case` and `when` becomes `switch` and `case`
32
- * ruby for loops become js for loops
33
- * `(1...4).step(2){` becomes `for (var i = 1; i < 4; i += 2) {`
34
- * `x.forEach { next }` becomes `x.forEach(function() {return})`
35
- * `lambda {}` and `proc {}` becomes `function() {}`
36
- * `class Person; end` becomes `function Person() {}`
37
- * instance methods become prototype methods
38
- * instance variables become underscored, `@name` becomes `this._name`
39
- * self is assigned to this is if used
40
- * Any block becomes and explicit argument `new Promise do; y(); end` becomes `new Promise(function() {y()})`
41
- * regular expressions are mapped to js
42
- * `raise` becomes `throw`
43
- * expressions enclosed in backtick operators (\`\`) and `%x{}` literals are
44
- evaluated in the context of the caller and the results are inserted
45
- into the generated JavaScript.
46
10
 
47
- Ruby attribute accessors, methods defined with no parameters and no
48
- parenthesis, as well as setter method definitions, are
49
- mapped to
50
- [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FObject%2FdefineProperty),
51
- so avoid these if you wish to target users running IE8 or lower.
11
+ Documentation
12
+ ---
52
13
 
53
- While both Ruby and JavaScript have open classes, Ruby unifies the syntax for
54
- defining and extending an existing class, whereas JavaScript does not. This
55
- means that Ruby2JS needs to be told when a class is being extended, which is
56
- done by prepending the `class` keyword with two plus signs, thus:
57
- `++class C; ...; end`.
14
+ * Visit **[ruby2js.com](https://www.ruby2js.com)** for detailed setup instructions and API reference.
58
15
 
59
- Filters may be provided to add Ruby-specific or framework specific
60
- behavior. Filters are essentially macro facilities that operate on
61
- an AST representation of the code.
16
+ * [Try Ruby2JS online](https://ruby2js.com/demo)
62
17
 
63
- See
64
- [notimplemented_spec](https://github.com/rubys/ruby2js/blob/master/spec/notimplemented_spec.rb)
65
- for a list of Ruby features _known_ to be not implemented.
66
18
 
67
19
  Synopsis
68
20
  ---
@@ -93,618 +45,6 @@ Enable ES2015 support:
93
45
  puts Ruby2JS.convert('"#{a}"', eslevel: 2015)
94
46
  ```
95
47
 
96
- Enable strict support:
97
-
98
- ```ruby
99
- puts Ruby2JS.convert('a=1', strict: true)
100
- ```
101
-
102
- Emit strict equality comparisons:
103
-
104
- ```ruby
105
- puts Ruby2JS.convert('a==1', comparison: :identity)
106
- ```
107
-
108
- Emit nullish coalescing operators:
109
-
110
- ```ruby
111
- puts Ruby2JS.convert('a || 1', or: :nullish)
112
- ```
113
-
114
- Emit underscored private fields (allowing subclass access):
115
-
116
- ```ruby
117
- puts Ruby2JS.convert('class C; def initialize; @f=1; end; end',
118
- eslevel: 2020, underscored_private: true)
119
- ```
120
-
121
- With [ExecJS](https://github.com/sstephenson/execjs):
122
- ```ruby
123
- require 'ruby2js/execjs'
124
- require 'date'
125
-
126
- context = Ruby2JS.compile(Date.today.strftime('d = new Date(%Y, %-m-1, %-d)'))
127
- puts context.eval('d.getYear()')+1900
128
- ```
129
-
130
- Conversions can be explored interactively using the
131
- [demo](https://github.com/rubys/ruby2js/blob/master/demo/ruby2js.rb) provided.
132
-
133
- Introduction
134
- ---
135
-
136
- JavaScript is a language where `0` is considered `false`, strings are
137
- immutable, and the behaviors for operators like `==` are, at best,
138
- [convoluted](http://zero.milosz.ca/).
139
-
140
- Any attempt to bridge the semantics of Ruby and JavaScript will involve
141
- trade-offs. Consider the following expression:
142
-
143
- ```ruby
144
- a[-1]
145
- ```
146
-
147
- Programmers who are familiar with Ruby will recognize that this returns the
148
- last element (or character) of an array (or string). However, the meaning is
149
- quite different if `a` is a Hash.
150
-
151
- One way to resolve this is to change the way indexing operators are evaluated,
152
- and to provide a runtime library that adds properties to global JavaScript
153
- objects to handle this. This is the approach that [Opal](http://opalrb.com/)
154
- takes. It is a fine approach, with a number of benefits. It also has some
155
- notable drawbacks. For example,
156
- [readability](http://opalrb.com/try/#code:a%20%3D%20%22abc%22%3B%20puts%20a[-1])
157
- and
158
- [compatibility with other frameworks](https://github.com/opal/opal/issues/400).
159
-
160
- Another approach is to simply accept JavaScript semantics for what they are.
161
- This would mean that negative indexes would return `undefined` for arrays
162
- and strings. This is the base approach provided by ruby2js.
163
-
164
- A third approach would be to do static transformations on the source in order
165
- to address common usage patterns or idioms. These transformations can even be
166
- occasionally unsafe, as long as the transformations themselves are opt-in.
167
- ruby2js provides a number of such filters, including one that handles negative
168
- indexes when passed as a literal. As indicated above, this is unsafe in that
169
- it will do the wrong thing when it encounters a hash index which is expressed
170
- as a literal constant negative one. My experience is that such is rare enough
171
- to be safely ignored, but YMMV. More troublesome, this also won’t work when
172
- the index is not a literal (e.g., `a[n]`) and the index happens to be
173
- negative at runtime.
174
-
175
- This quickly gets into gray areas. `each` in Ruby is a common method that
176
- facilitates iteration over arrays. `forEach` is the JavaScript equivalent.
177
- Mapping this is fine until you start using a framework like jQuery which
178
- provides a function named [each](http://api.jquery.com/jQuery.each/).
179
-
180
- Fortunately, Ruby provides `?` and `!` as legal suffixes for method names,
181
- Ruby2js filters do an exact match, so if you select a filter that maps `each`
182
- to `forEach`, `each!` will pass through the filter. The final code that emits
183
- JavaScript function calls and parameter accesses will strip off these
184
- suffixes.
185
-
186
- This approach works well if it is an occasional change, but if the usage is
187
- pervasive, most filters support options to `exclude` a list of mappings,
188
- for example:
189
-
190
- ```ruby
191
- puts Ruby2JS.convert('jQuery("li").each {|index| ...}', exclude: :each)
192
- ```
193
-
194
- Alternatively, you can change the default:
195
-
196
- ```ruby
197
- Ruby2JS::Filter.exclude :each
198
- ```
199
-
200
- Static transformations and runtime libraries aren't aren’t mutually exclusive.
201
- With enough of each, one could reproduce any functionality desired. Just be
202
- forewarned, that implementing a function like `method_missing` would require a
203
- _lot_ of work.
204
-
205
- Integrations
206
- ---
207
-
208
- While this is a low level library suitable for DIY integration, one of the
209
- obvious uses of a tool that produces JavaScript is by web servers. Ruby2JS
210
- includes several integrations:
211
-
212
- * [CGI](https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/cgi.rb)
213
- * [Sinatra](https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/sinatra.rb)
214
- * [Rails](https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/rails.rb)
215
- * [Haml](https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/haml.rb)
216
-
217
- As you might expect, CGI is a bit sluggish. By contrast, Sinatra and Rails
218
- are quite speedy as the bulk of the time is spent on the initial load of the
219
- required libraries.
220
-
221
- For easy integration with Webpack (and Webpacker in Rails 5+), you can use the
222
- [rb2js-loader](https://github.com/whitefusionhq/rb2js-loader) plugin.
223
-
224
- Filters
225
- ---
226
-
227
- In general, making use of a filter is as simple as requiring it. If multiple
228
- filters are selected, they will all be applied in parallel in one pass through
229
- the script.
230
-
231
- * <a id="return" href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter/return.rb">return</a>
232
- adds `return` to the last expression in functions.
233
-
234
- * <a id="require" href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter/require.rb">require</a>
235
- supports `require` and `require_relative` statements. Contents of files
236
- that are required are converted to JavaScript and expanded inline.
237
- `require` function calls in expressions are left alone.
238
-
239
- * <a id="camelCase" href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter/camelCase.rb">camelCase</a>
240
- converts `underscore_case` to `camelCase`. See
241
- [camelcase_spec](https://github.com/rubys/ruby2js/blob/master/spec/camelcase_spec.rb)
242
- for examples.
243
-
244
- * <a id="functions" href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter/functions.rb">functions</a>
245
-
246
- * `.abs` becomes `Math.abs()`
247
- * `.all?` becomes `.every`
248
- * `.any?` becomes `.some`
249
- * `.ceil` becomes `Math.ceil()`
250
- * `.chr` becomes `fromCharCode`
251
- * `.clear` becomes `.length = 0`
252
- * `.delete` becomes `delete target[arg]`
253
- * `.downcase` becomes `.toLowerCase`
254
- * `.each` becomes `.forEach`
255
- * `.each_key` becomes `for (i in ...) {}`
256
- * `.each_pair` becomes `for (var key in item) {var value = item[key]; ...}`
257
- * `.each_value` becomes `.forEach`
258
- * `.each_with_index` becomes `.forEach`
259
- * `.end_with?` becomes `.slice(-arg.length) == arg`
260
- * `.empty?` becomes `.length == 0`
261
- * `.find_index` becomes `findIndex`
262
- * `.first` becomes `[0]`
263
- * `.first(n)` becomes `.slice(0, n)`
264
- * `.floor` becomes `Math.floor()`
265
- * `.gsub` becomes `replace(//g)`
266
- * `.include?` becomes `.indexOf() != -1`
267
- * `.inspect` becomes `JSON.stringify()`
268
- * `.keys()` becomes `Object.keys()`
269
- * `.last` becomes `[*.length-1]`
270
- * `.last(n)` becomes `.slice(*.length-1, *.length)`
271
- * `.lstrip` becomes `.replace(/^\s+/, "")`
272
- * `.max` becomes `Math.max.apply(Math)`
273
- * `.merge` becomes `Object.assign({}, ...)`
274
- * `.merge!` becomes `Object.assign()`
275
- * `.min` becomes `Math.min.apply(Math)`
276
- * `.nil?` becomes `== null`
277
- * `.ord` becomes `charCodeAt(0)`
278
- * `puts` becomes `console.log`
279
- * `.replace` becomes `.length = 0; ...push.apply(*)`
280
- * `.respond_to?` becomes `right in left`
281
- * `.rstrip` becomes `.replace(/s+$/, "")`
282
- * `.scan` becomes `.match(//g)`
283
- * `.start_with?` becomes `.substring(0, arg.length) == arg`
284
- * `.upto(lim)` becomes `for (var i=num; i<=lim; i+=1)`
285
- * `.downto(lim)` becomes `for (var i=num; i>=lim; i-=1)`
286
- * `.step(lim, n).each` becomes `for (var i=num; i<=lim; i+=n)`
287
- * `.step(lim, -n).each` becomes `for (var i=num; i>=lim; i-=n)`
288
- * `(0..a).to_a` becomes `Array.apply(null, {length: a}).map(Function.call, Number)`
289
- * `(b..a).to_a` becomes `Array.apply(null, {length: (a-b+1)}).map(Function.call, Number).map(function (idx) { return idx+b })`
290
- * `(b...a).to_a` becomes `Array.apply(null, {length: (a-b)}).map(Function.call, Number).map(function (idx) { return idx+b })`
291
- * `.strip` becomes `.trim`
292
- * `.sub` becomes `.replace`
293
- * `.tap {|n| n}` becomes `(function(n) {n; return n})(...)`
294
- * `.to_f` becomes `parseFloat`
295
- * `.to_i` becomes `parseInt`
296
- * `.to_s` becomes `.to_String`
297
- * `.upcase` becomes `.toUpperCase`
298
- * `.yield_self {|n| n}` becomes `(function(n) {return n})(...)`
299
- * `[-n]` becomes `[*.length-n]` for literal values of `n`
300
- * `[n...m]` becomes `.slice(n,m)`
301
- * `[n..m]` becomes `.slice(n,m+1)`
302
- * `[/r/, n]` becomes `.match(/r/)[n]`
303
- * `[/r/, n]=` becomes `.replace(/r/, ...)`
304
- * `(1..2).each {|i| ...}` becomes `for (var i=1 i<=2; i+=1)`
305
- * `"string" * length` becomes `new Array(length + 1).join("string")`
306
- * `.sub!` and `.gsub!` become equivalent `x = x.replace` statements
307
- * `.map!`, `.reverse!`, and `.select` become equivalent
308
- `.splice(0, .length, *.method())` statements
309
- * `@foo.call(args)` becomes `this._foo(args)`
310
- * `@@foo.call(args)` becomes `this.constructor._foo(args)`
311
- * `Array(x)` becomes `Array.prototype.slice.call(x)`
312
- * `delete x` becomes `delete x` (note lack of parenthesis)
313
- * `setInterval` and `setTimeout` allow block to be treated as the
314
- first parameter on the call
315
- * for the following methods, if the block consists entirely of a simple
316
- expression (or ends with one), a `return` is added prior to the
317
- expression: `sub`, `gsub`, `any?`, `all?`, `map`, `find`, `find_index`.
318
- * New classes subclassed off of `Exception` will become subclassed off
319
- of `Error` instead; and default constructors will be provided
320
- * `loop do...end` will be replaced with `while (true) {...}`
321
- * `raise Exception.new(...)` will be replaced with `throw new Error(...)`
322
- * `block_given?` will check for the presence of optional argument `_implicitBlockYield` which is a function made accessible through the use of `yield` in a method body.
323
-
324
- Additionally, there is one mapping that will only be done if explicitly
325
- included (pass `include: :class` as a `convert` option to enable):
326
-
327
- * `.class` becomes `.constructor`
328
-
329
- * <a id="tagged_templates" href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter/tagged_templates.rb">tagged_templates</a>
330
-
331
- Allows you to turn certain method calls with a string argument into tagged
332
- template literals. By default it supports html and css, so you can write
333
- `html "<div>#{1+2}</div>"` which converts to `` html`<div>${1+2}</div>` ``.
334
- Works nicely with squiggly heredocs for multi-line templates as well. If you
335
- need to configure the tag names yourself, pass a `template_literal_tags`
336
- option to `convert` with an array of tag name symbols.
337
-
338
- Note: these conversions are only done if eslevel >= 2015
339
-
340
- * <a href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter/esm.rb">esm</a>
341
-
342
- Provides conversion of import and export statements for use with modern ES builders like Webpack.
343
-
344
- Examples:
345
-
346
- **import**
347
-
348
- ```ruby
349
- import "./index.scss"
350
- # => import "./index.scss"
351
-
352
- import Something from "./lib/something"
353
- # => import Something from "./lib/something"
354
-
355
- import Something, "./lib/something"
356
- # => import Something from "./lib/something"
357
-
358
- import [ LitElement, html, css ], from: "lit-element"
359
- # => import { LitElement, html, css } from "lit-element"
360
-
361
- import React, from: "react"
362
- # => import React from "react"
363
-
364
- import React, as: "*", from: "react"
365
- # => import React as * from "react"
366
- ```
367
-
368
- **export**
369
-
370
- ```ruby
371
- export hash = { ab: 123 }
372
- # => export const hash = {ab: 123};
373
-
374
- export func = ->(x) { x * 10 }
375
- # => export const func = x => x * 10;
376
-
377
- export def multiply(x, y)
378
- return x * y
379
- end
380
- # => export function multiply(x, y) {
381
- # return x * y
382
- # }
383
-
384
- export default class MyClass
385
- end
386
- # => export default class MyClass {
387
- # };
388
-
389
- # or final export statement:
390
- export [ one, two, default: three ]
391
- # => export { one, two, three as default }
392
- ```
393
-
394
- * <a id="node" href="https://github.com/rubys/ruby2js/blob/master/spec/node_spec.rb">node</a>
395
-
396
- * `` `command` `` becomes `child_process.execSync("command", {encoding: "utf8"})`
397
- * `ARGV` becomes `process.argv.slice(2)`
398
- * `__dir__` becomes `__dirname`
399
- * `Dir.chdir` becomes `process.chdir`
400
- * `Dir.entries` becomes `fs.readdirSync`
401
- * `Dir.mkdir` becomes `fs.mkdirSync`
402
- * `Dir.mktmpdir` becomes `fs.mkdtempSync`
403
- * `Dir.pwd` becomes `process.cwd`
404
- * `Dir.rmdir` becomes `fs.rmdirSync`
405
- * `ENV` becomes `process.env`
406
- * `__FILE__` becomes `__filename`
407
- * `File.chmod` becomes `fs.chmodSync`
408
- * `File.chown` becomes `fs.chownSync`
409
- * `File.cp` becomes `fs.copyFileSync`
410
- * `File.exist?` becomes `fs.existsSync`
411
- * `File.lchmod` becomes `fs.lchmodSync`
412
- * `File.link` becomes `fs.linkSync`
413
- * `File.ln` becomes `fs.linkSync`
414
- * `File.lstat` becomes `fs.lstatSync`
415
- * `File.read` becomes `fs.readFileSync`
416
- * `File.readlink` becomes `fs.readlinkSync`
417
- * `File.realpath` becomes `fs.realpathSync`
418
- * `File.rename` becomes `fs.renameSync`
419
- * `File.stat` becomes `fs.statSync`
420
- * `File.symlink` becomes `fs.symlinkSync`
421
- * `File.truncate` becomes `fs.truncateSync`
422
- * `File.unlink` becomes `fs.unlinkSync`
423
- * `FileUtils.cd` becomes `process.chdir`
424
- * `FileUtils.cp` becomes `fs.copyFileSync`
425
- * `FileUtils.ln` becomes `fs.linkSync`
426
- * `FileUtils.ln_s` becomes `fs.symlinkSync`
427
- * `FileUtils.mkdir` becomes `fs.mkdirSync`
428
- * `FileUtils.mv` becomes `fs.renameSync`
429
- * `FileUtils.pwd` becomes `process.cwd`
430
- * `FileUtils.rm` becomes `fs.unlinkSync`
431
- * `IO.read` becomes `fs.readFileSync`
432
- * `IO.write` becomes `fs.writeFileSync`
433
- * `system` becomes `child_process.execSync(..., {stdio: "inherit"})`
434
-
435
- * <a id="nokogiri" href="https://github.com/rubys/ruby2js/blob/master/spec/nokogiri.rb">nokogiri</a>
436
- * `add_child` becomes `appendChild`
437
- * `add_next_sibling` becomes `node.parentNode.insertBefore(sibling, node.nextSibling)`
438
- * `add_previous_sibling` becomes `node.parentNode.insertBefore(sibling, node)`
439
- * `after` becomes `node.parentNode.insertBefore(sibling, node.nextSibling)`
440
- * `at` becomes `querySelector`
441
- * `attr` becomes `getAttribute`
442
- * `attribute` becomes `getAttributeNode`
443
- * `before` becomes `node.parentNode.insertBefore(sibling, node)`
444
- * `cdata?` becomes `node.nodeType === Node.CDATA_SECTION_NODE`
445
- * `children` becomes `childNodes`
446
- * `comment?` becomes `node.nodeType === Node.COMMENT_NODE`
447
- * `content` becomes `textContent`
448
- * `create_element` becomes `createElement`
449
- * `document` becomes `ownerDocument`
450
- * `element?` becomes `node.nodeType === Node.ELEMENT_NODE`
451
- * `fragment?` becomes `node.nodeType === Node.FRAGMENT_NODE`
452
- * `get_attribute` becomes `getAttribute`
453
- * `has_attribute` becomes `hasAttribute`
454
- * `inner_html` becomes `innerHTML`
455
- * `key?` becomes `hasAttribute`
456
- * `name` becomes `nextSibling`
457
- * `next` becomes `nodeName`
458
- * `next=` becomes `node.parentNode.insertBefore(sibling,node.nextSibling)`
459
- * `next_element` becomes `nextElement`
460
- * `next_sibling` becomes `nextSibling`
461
- * `Nokogiri::HTML5` becomes `new JSDOM().window.document`
462
- * `Nokogiri::HTML5.parse` becomes `new JSDOM().window.document`
463
- * `Nokogiri::HTML` becomes `new JSDOM().window.document`
464
- * `Nokogiri::HTML.parse` becomes `new JSDOM().window.document`
465
- * `Nokogiri::XML::Node.new` becomes `document.createElement()`
466
- * `parent` becomes `parentNode`
467
- * `previous=` becomes `node.parentNode.insertBefore(sibling, node)`
468
- * `previous_element` becomes `previousElement`
469
- * `previous_sibling` becomes `previousSibling`
470
- * `processing_instruction?` becomes `node.nodeType === Node.PROCESSING_INSTRUCTION_NODE`
471
- * `remove_attribute` becomes `removeAttribute`
472
- * `root` becomes `documentElement`
473
- * `search` becomes `querySelectorAll`
474
- * `set_attribute` becomes `setAttribute`
475
- * `text?` becomes `node.nodeType === Node.TEXT_NODE`
476
- * `text` becomes `textContent`
477
- * `to_html` becomes `outerHTML`
478
-
479
- * <a id="underscore" href="https://github.com/rubys/ruby2js/blob/master/spec/underscore.rb">underscore</a>
480
-
481
- * `.clone()` becomes `_.clone()`
482
- * `.compact()` becomes `_.compact()`
483
- * `.count_by {}` becomes `_.countBy {}`
484
- * `.find {}` becomes `_.find {}`
485
- * `.find_by()` becomes `_.findWhere()`
486
- * `.flatten()` becomes `_.flatten()`
487
- * `.group_by {}` becomes `_.groupBy {}`
488
- * `.has_key?()` becomes `_.has()`
489
- * `.index_by {}` becomes `_.indexBy {}`
490
- * `.invert()` becomes `_.invert()`
491
- * `.invoke(&:n)` becomes `_.invoke(, :n)`
492
- * `.map(&:n)` becomes `_.pluck(, :n)`
493
- * `.merge!()` becomes `_.extend()`
494
- * `.merge()` becomes `_.extend({}, )`
495
- * `.reduce {}` becomes `_.reduce {}`
496
- * `.reduce()` becomes `_.reduce()`
497
- * `.reject {}` becomes `_.reject {}`
498
- * `.sample()` becomes `_.sample()`
499
- * `.select {}` becomes `_.select {}`
500
- * `.shuffle()` becomes `_.shuffle()`
501
- * `.size()` becomes `_.size()`
502
- * `.sort()` becomes `_.sort_by(, _.identity)`
503
- * `.sort_by {}` becomes `_.sortBy {}`
504
- * `.times {}` becomes `_.times {}`
505
- * `.values()` becomes `_.values()`
506
- * `.where()` becomes `_.where()`
507
- * `.zip()` becomes `_.zip()`
508
- * `(n...m)` becomes `_.range(n, m)`
509
- * `(n..m)` becomes `_.range(n, m+1)`
510
- * `.compact!`, `.flatten!`, `shuffle!`, `reject!`, `sort_by!`, and
511
- `.uniq` become equivalent `.splice(0, .length, *.method())` statements
512
- * for the following methods, if the block consists entirely of a simple
513
- expression (or ends with one), a `return` is added prior to the
514
- expression: `reduce`, `sort_by`, `group_by`, `index_by`, `count_by`,
515
- `find`, `select`, `reject`.
516
- * `is_a?` and `kind_of?` map to `Object.prototype.toString.call() ===
517
- "[object #{type}]" for the following types: `Arguments`, `Boolean`,
518
- `Date`, `Error`, `Function`, `Number`, `Object`, `RegExp`, `String`; and
519
- maps Ruby names to JavaScript equivalents for `Exception`, `Float`,
520
- `Hash`, `Proc`, and `Regexp`. Additionally, `is_a?` and `kind_of?` map
521
- to `Array.isArray()` for `Array`.
522
-
523
- * <a id="jquery" href="https://github.com/rubys/ruby2js/blob/master/spec/jquery.rb">jquery</a>
524
-
525
- * maps Ruby unary operator `~` to jQuery `$` function
526
- * maps Ruby attribute syntax to jquery attribute syntax
527
- * `.to_a` becomes `toArray`
528
- * maps `$$` to jQuery `$` function
529
- * defaults the fourth parameter of $$.post to `"json"`, allowing Ruby block
530
- syntax to be used for the success function.
531
-
532
- * <a id="minitest-jasmine" href="https://github.com/rubys/ruby2js/blob/master/spec/minitest-jasmine.rb">minitest-jasmine</a>
533
- * maps subclasses of `Minitest::Test` to `describe` calls
534
- * maps `test_` methods inside subclasses of `Minitest::Test` to `it` calls
535
- * maps `setup`, `teardown`, `before`, and `after` calls to `beforeEach`
536
- and `afterEach` calls
537
- * maps `assert` and `refute` calls to `expect`...`toBeTruthy()` and
538
- `toBeFalsy` calls
539
- * maps `assert_equal`, `refute_equal`, `.must_equal` and `.cant_equal`
540
- calls to `expect`...`toBe()` calls
541
- * maps `assert_in_delta`, `refute_in_delta`, `.must_be_within_delta`,
542
- `.must_be_close_to`, `.cant_be_within_delta`, and `.cant_be_close_to`
543
- calls to `expect`...`toBeCloseTo()` calls
544
- * maps `assert_includes`, `refute_includes`, `.must_include`, and
545
- `.cant_include` calls to `expect`...`toContain()` calls
546
- * maps `assert_match`, `refute_match`, `.must_match`, and `.cant_match`
547
- calls to `expect`...`toMatch()` calls
548
- * maps `assert_nil`, `refute_nil`, `.must_be_nil`, and `.cant_be_nill` calls
549
- to `expect`...`toBeNull()` calls
550
- * maps `assert_operator`, `refute_operator`, `.must_be`, and `.cant_be`
551
- calls to `expect`...`toBeGreaterThan()` or `toBeLessThan` calls
552
-
553
- * <a id="cjs" href="https://github.com/rubys/ruby2js/blob/master/spec/cjs">cjs</a>
554
- * maps `export def f` to `exports.f =`
555
- * maps `export async def f` to `exports.f = async`
556
- * maps `export v =` to `exports.v =`
557
- * maps `export default proc` to `module.exports =`
558
- * maps `export default async proc` to `module.exports = async`
559
- * maps `export default` to `module.exports =`
560
-
561
- * <a id="matchAll" href="https://github.com/rubys/ruby2js/blob/master/spec/matchAll">matchAll</a>
562
-
563
- For ES level < 2020:
564
-
565
- * maps `str.matchAll(pattern).forEach {}` to
566
- `while (match = pattern.exec(str)) {}`
567
-
568
- Note `pattern` must be a simple variable with a value of a regular
569
- expression with the `g` flag set at runtime.
570
-
571
- [Wunderbar](https://github.com/rubys/wunderbar) includes additional demos:
572
-
573
- * [chat](https://github.com/rubys/wunderbar/blob/master/demo/chat.rb),
574
- [diskusage](https://github.com/rubys/wunderbar/blob/master/demo/diskusage.rb),
575
- and [wiki](https://github.com/rubys/wunderbar/blob/master/demo/wiki.rb) make
576
- use of the jquery filter.
577
-
578
- ES2015 support
579
- ---
580
-
581
- When option `eslevel: 2015` is provided, the following additional
582
- conversions are made:
583
-
584
- * `"#{a}"` becomes <code>\`${a}\`</code>
585
- * `a = 1` becomes `let a = 1`
586
- * `A = 1` becomes `const A = 1`
587
- * `a, b = b, a` becomes `[a, b] = [b, a]`
588
- * `a, (foo, *bar) = x` becomes `let [a, [foo, ...bar]] = x`
589
- * `def f(a, (foo, *bar))` becomes `function f(a, [foo, ...bar])`
590
- * `def a(b=1)` becomes `function a(b=1)`
591
- * `def a(*b)` becomes `function a(...b)`
592
- * `.each_value` becomes `for (i of ...) {}`
593
- * `a(*b)` becomes `a(...b)`
594
- * `"#{a}"` becomes <code>\`${a}\`</code>
595
- * `lambda {|x| x}` becomes `(x) => {return x}`
596
- * `proc {|x| x}` becomes `(x) => {x}`
597
- * `a {|x|}` becomes `a((x) => {})`
598
- * `class Person; end` becomes `class Person {}`
599
- * `(0...a).to_a` becomes `[...Array(a).keys()]`
600
- * `(0..a).to_a` becomes `[...Array(a+1).keys()]`
601
- * `(b..a).to_a` becomes `Array.from({length: (a-b+1)}, (_, idx) => idx+b)`
602
-
603
- ES2015 class support includes constructors, super, methods, class methods,
604
- instance methods, instance variables, class variables, getters, setters,
605
- attr_accessor, attr_reader, attr_writer, etc.
606
-
607
- Additionally, the `functions` filter will provide the following conversion:
608
-
609
- * `Array(x)` becomes `Array.from(x)`
610
- * `.inject(n) {}` becomes `.reduce(() => {}, n)`
611
-
612
- Finally, keyword arguments and optional keyword arguments will be mapped to
613
- parameter detructuring.
614
-
615
- ES2016 support
616
- ---
617
-
618
- When option `eslevel: 2016` is provided, the following additional
619
- conversion is made:
620
-
621
- * `a ** b` becomes `a ** b`
622
- * `.include?` becomes `.includes`
623
-
624
- ES2017 support
625
- ---
626
-
627
- When option `eslevel: 2017` is provided, the following additional
628
- conversions are made by the `functions` filter:
629
-
630
- * `.values()` becomes `Object.values()`
631
- * `.entries()` becomes `Object.entries()`
632
- * `.each_pair {}` becomes `for (let [key, value] of Object.entries()) {}'
633
-
634
- async support:
635
-
636
- * `async def` becomes `async function`
637
- * `async lambda` becomes `async =>`
638
- * `async proc` becomes `async =>`
639
- * `async ->` becomes `async =>`
640
- * `foo bar, async do...end` becomes `foo(bar, async () => {})`
641
-
642
- ES2018 support
643
- ---
644
-
645
- When option `eslevel: 2018` is provided, the following additional
646
- conversion is made by the `functions` filter:
647
-
648
- * `.merge` becomes `{...a, ...b}`
649
-
650
- Additionally, rest arguments can now be used with keyword arguments and
651
- optional keyword arguments.
652
-
653
- ES2019 support
654
- ---
655
-
656
- When option `eslevel: 2019` is provided, the following additional
657
- conversion is made by the `functions` filter:
658
-
659
- * `.flatten` becomes `.flat(Infinity)`
660
- * `.lstrip` becomes `.trimEnd
661
- * `.rstrip` becomes `.trimStart
662
- * `a.to_h` becomes `Object.fromEntries(a)`
663
- * `Hash[a]` becomes `Object.fromEntries(a)`
664
-
665
- Additionally, `rescue` without a variable will map to `catch` without a
666
- variable.
667
-
668
- ES2020 support
669
- ---
670
-
671
- When option `eslevel: 2020` is provided, the following additional
672
- conversions are made:
673
-
674
- * `@x` becomes `this.#x`
675
- * `@@x` becomes `ClassName.#x`
676
- * `a&.b` becomes `a?.b`
677
- * `.scan` becomes `Array.from(str.matchAll(/.../g), s => s.slice(1))`
678
-
679
- ES2021 support
680
- ---
681
-
682
- When option `eslevel: 2021` is provided, the following additional
683
- conversions are made:
684
-
685
- * `x ||= 1` becomes `x ||= 1`
686
- * `x &&= 1` becomes `x &&= 1`
687
-
688
- Picking a Ruby to JS mapping tool
689
- ---
690
-
691
- > dsl — A domain specific language, where code is written in one language and
692
- > errors are given in another.
693
- > -- [Devil’s Dictionary of Programming](http://programmingisterrible.com/post/65781074112/devils-dictionary-of-programming)
694
-
695
- If you simply want to get a job done, and would like a mature and tested
696
- framework, and only use one of the many integrations that
697
- [Opal](http://opalrb.com/) provides, then Opal is the way to go right now.
698
-
699
- ruby2js is for those that want to produce JavaScript that looks like it
700
- wasn’t machine generated, and want the absolute bare minimum in terms of
701
- limitations as to what JavaScript can be produced.
702
-
703
- [Try](http://intertwingly.net/projects/ruby2js.cgi/all) for yourself.
704
- [Compare](http://opalrb.com/try/#code:).
705
-
706
- And, of course, the right solution might be to use
707
- [CoffeeScript](http://coffeescript.org/) instead.
708
48
 
709
49
  License
710
50
  ---