ruby2js 3.5.1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -662
  3. data/lib/ruby2js.rb +61 -10
  4. data/lib/ruby2js/converter.rb +10 -4
  5. data/lib/ruby2js/converter/assign.rb +159 -0
  6. data/lib/ruby2js/converter/begin.rb +7 -2
  7. data/lib/ruby2js/converter/case.rb +7 -2
  8. data/lib/ruby2js/converter/class.rb +77 -21
  9. data/lib/ruby2js/converter/class2.rb +102 -31
  10. data/lib/ruby2js/converter/def.rb +7 -3
  11. data/lib/ruby2js/converter/dstr.rb +8 -3
  12. data/lib/ruby2js/converter/hash.rb +9 -5
  13. data/lib/ruby2js/converter/hide.rb +13 -0
  14. data/lib/ruby2js/converter/if.rb +10 -2
  15. data/lib/ruby2js/converter/import.rb +35 -4
  16. data/lib/ruby2js/converter/kwbegin.rb +9 -2
  17. data/lib/ruby2js/converter/literal.rb +14 -2
  18. data/lib/ruby2js/converter/module.rb +41 -4
  19. data/lib/ruby2js/converter/opasgn.rb +8 -0
  20. data/lib/ruby2js/converter/send.rb +45 -5
  21. data/lib/ruby2js/converter/vasgn.rb +5 -0
  22. data/lib/ruby2js/converter/xstr.rb +1 -1
  23. data/lib/ruby2js/demo.rb +53 -0
  24. data/lib/ruby2js/es2022.rb +5 -0
  25. data/lib/ruby2js/es2022/strict.rb +3 -0
  26. data/lib/ruby2js/filter.rb +9 -1
  27. data/lib/ruby2js/filter/active_functions.rb +44 -0
  28. data/lib/ruby2js/filter/camelCase.rb +4 -3
  29. data/lib/ruby2js/filter/cjs.rb +2 -0
  30. data/lib/ruby2js/filter/esm.rb +133 -7
  31. data/lib/ruby2js/filter/functions.rb +107 -98
  32. data/lib/ruby2js/filter/{wunderbar.rb → jsx.rb} +29 -7
  33. data/lib/ruby2js/filter/node.rb +95 -74
  34. data/lib/ruby2js/filter/nokogiri.rb +15 -41
  35. data/lib/ruby2js/filter/react.rb +191 -56
  36. data/lib/ruby2js/filter/require.rb +100 -5
  37. data/lib/ruby2js/filter/return.rb +15 -1
  38. data/lib/ruby2js/filter/securerandom.rb +33 -0
  39. data/lib/ruby2js/filter/stimulus.rb +185 -0
  40. data/lib/ruby2js/filter/vue.rb +9 -0
  41. data/lib/ruby2js/jsx.rb +291 -0
  42. data/lib/ruby2js/namespace.rb +75 -0
  43. data/lib/ruby2js/rails.rb +15 -9
  44. data/lib/ruby2js/serializer.rb +3 -1
  45. data/lib/ruby2js/version.rb +3 -3
  46. data/ruby2js.gemspec +1 -1
  47. metadata +14 -5
  48. data/lib/ruby2js/filter/esm_migration.rb +0 -72
  49. data/lib/ruby2js/filter/fast-deep-equal.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f659d6312b558d08a3ca6c1f30183d602445ed580910715909ea2b4334a114a9
4
- data.tar.gz: 3f07730f5727ea25b95e095c7b45c19f3fb85dcc5d640e822a3a629fa88b8056
3
+ metadata.gz: 064d9dc5d65f6e2456e91d951e2f4311b776221422a77d59a8cfd466ceb5056d
4
+ data.tar.gz: ffc274a55ceb9a1280f776c00da8c296dc10672150174135931547cd056a3520
5
5
  SHA512:
6
- metadata.gz: a85a3d7f144485575608cd136d764e36868d095fb8dbf4f6bc8b8adb8a218379775eeb12934c148574cac0f03dc6e12fc6223ef302973b5f4cb0a0629ba41de4
7
- data.tar.gz: 4c154cd393ce76cc0f72e23180fbfdd1b0b8e876a5d60686d246e9f53effff1c4f4ce4ea3c5573cf9ac5cb7cfcda3d2300ee865c2d8d21855a717fa8f203de4a
6
+ metadata.gz: 8fe9775803716fce25697adaf59a8afb57f4de499052a66847f18dfdd59c1f73b52d77be473adf762a57693a0896a6c90ed275a2b9152b5b6b548ca454e6d9ae
7
+ data.tar.gz: aa901eab47bba051a69a0ee132b69e7754aa5da49ff2e7e22560303a4c0fd02cdb727d4297c13098f3a6f82cf20788f4657c7ec21d145e66533875933d1a5154
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,615 +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
- * `.all?` becomes `.every`
247
- * `.any?` becomes `.some`
248
- * `.chr` becomes `fromCharCode`
249
- * `.clear` becomes `.length = 0`
250
- * `.delete` becomes `delete target[arg]`
251
- * `.downcase` becomes `.toLowerCase`
252
- * `.each` becomes `.forEach`
253
- * `.each_key` becomes `for (i in ...) {}`
254
- * `.each_pair` becomes `for (var key in item) {var value = item[key]; ...}`
255
- * `.each_value` becomes `.forEach`
256
- * `.each_with_index` becomes `.forEach`
257
- * `.end_with?` becomes `.slice(-arg.length) == arg`
258
- * `.empty?` becomes `.length == 0`
259
- * `.find_index` becomes `findIndex`
260
- * `.first` becomes `[0]`
261
- * `.first(n)` becomes `.slice(0, n)`
262
- * `.gsub` becomes `replace(//g)`
263
- * `.include?` becomes `.indexOf() != -1`
264
- * `.inspect` becomes `JSON.stringify()`
265
- * `.keys()` becomes `Object.keys()`
266
- * `.last` becomes `[*.length-1]`
267
- * `.last(n)` becomes `.slice(*.length-1, *.length)`
268
- * `.lstrip` becomes `.replace(/^\s+/, "")`
269
- * `.max` becomes `Math.max.apply(Math)`
270
- * `.merge` becomes `Object.assign({}, ...)`
271
- * `.merge!` becomes `Object.assign()`
272
- * `.min` becomes `Math.min.apply(Math)`
273
- * `.nil?` becomes `== null`
274
- * `.ord` becomes `charCodeAt(0)`
275
- * `puts` becomes `console.log`
276
- * `.replace` becomes `.length = 0; ...push.apply(*)`
277
- * `.respond_to?` becomes `right in left`
278
- * `.rstrip` becomes `.replace(/s+$/, "")`
279
- * `.scan` becomes `.match(//g)`
280
- * `.start_with?` becomes `.substring(0, arg.length) == arg`
281
- * `.upto(lim)` becomes `for (var i=num; i<=lim; i+=1)`
282
- * `.downto(lim)` becomes `for (var i=num; i>=lim; i-=1)`
283
- * `.step(lim, n).each` becomes `for (var i=num; i<=lim; i+=n)`
284
- * `.step(lim, -n).each` becomes `for (var i=num; i>=lim; i-=n)`
285
- * `(0..a).to_a` becomes `Array.apply(null, {length: a}).map(Function.call, Number)`
286
- * `(b..a).to_a` becomes `Array.apply(null, {length: (a-b+1)}).map(Function.call, Number).map(function (idx) { return idx+b })`
287
- * `(b...a).to_a` becomes `Array.apply(null, {length: (a-b)}).map(Function.call, Number).map(function (idx) { return idx+b })`
288
- * `.strip` becomes `.trim`
289
- * `.sub` becomes `.replace`
290
- * `.tap {|n| n}` becomes `(function(n) {n; return n})(...)`
291
- * `.to_f` becomes `parseFloat`
292
- * `.to_i` becomes `parseInt`
293
- * `.to_s` becomes `.to_String`
294
- * `.upcase` becomes `.toUpperCase`
295
- * `.yield_self {|n| n}` becomes `(function(n) {return n})(...)`
296
- * `[-n]` becomes `[*.length-n]` for literal values of `n`
297
- * `[n...m]` becomes `.slice(n,m)`
298
- * `[n..m]` becomes `.slice(n,m+1)`
299
- * `[/r/, n]` becomes `.match(/r/)[n]`
300
- * `[/r/, n]=` becomes `.replace(/r/, ...)`
301
- * `(1..2).each {|i| ...}` becomes `for (var i=1 i<=2; i+=1)`
302
- * `"string" * length` becomes `new Array(length + 1).join("string")`
303
- * `.sub!` and `.gsub!` become equivalent `x = x.replace` statements
304
- * `.map!`, `.reverse!`, and `.select` become equivalent
305
- `.splice(0, .length, *.method())` statements
306
- * `@foo.call(args)` becomes `this._foo(args)`
307
- * `@@foo.call(args)` becomes `this.constructor._foo(args)`
308
- * `Array(x)` becomes `Array.prototype.slice.call(x)`
309
- * `delete x` becomes `delete x` (note lack of parenthesis)
310
- * `setInterval` and `setTimeout` allow block to be treated as the
311
- first parameter on the call
312
- * for the following methods, if the block consists entirely of a simple
313
- expression (or ends with one), a `return` is added prior to the
314
- expression: `sub`, `gsub`, `any?`, `all?`, `map`, `find`, `find_index`.
315
- * New classes subclassed off of `Exception` will become subclassed off
316
- of `Error` instead; and default constructors will be provided
317
- * `loop do...end` will be replaced with `while (true) {...}`
318
- * `raise Exception.new(...)` will be replaced with `throw new Error(...)`
319
- * `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.
320
-
321
- Additionally, there is one mapping that will only be done if explicitly
322
- included (pass `include: :class` as a `convert` option to enable):
323
-
324
- * `.class` becomes `.constructor`
325
-
326
- * <a id="tagged_templates" href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter/tagged_templates.rb">tagged_templates</a>
327
-
328
- Allows you to turn certain method calls with a string argument into tagged
329
- template literals. By default it supports html and css, so you can write
330
- `html "<div>#{1+2}</div>"` which converts to `` html`<div>${1+2}</div>` ``.
331
- Works nicely with squiggly heredocs for multi-line templates as well. If you
332
- need to configure the tag names yourself, pass a `template_literal_tags`
333
- option to `convert` with an array of tag name symbols.
334
-
335
- Note: these conversions are only done if eslevel >= 2015
336
-
337
- * <a href="https://github.com/rubys/ruby2js/blob/master/lib/ruby2js/filter/esm.rb">esm</a>
338
-
339
- Provides conversion of import and export statements for use with modern ES builders like Webpack.
340
-
341
- Examples:
342
-
343
- **import**
344
-
345
- ```ruby
346
- import "./index.scss"
347
- # => import "./index.scss"
348
-
349
- import Something from "./lib/something"
350
- # => import Something from "./lib/something"
351
-
352
- import Something, "./lib/something"
353
- # => import Something from "./lib/something"
354
-
355
- import [ LitElement, html, css ], from: "lit-element"
356
- # => import { LitElement, html, css } from "lit-element"
357
-
358
- import React, from: "react"
359
- # => import React from "react"
360
-
361
- import React, as: "*", from: "react"
362
- # => import React as * from "react"
363
- ```
364
-
365
- **export**
366
-
367
- ```ruby
368
- export hash = { ab: 123 }
369
- # => export const hash = {ab: 123};
370
-
371
- export func = ->(x) { x * 10 }
372
- # => export const func = x => x * 10;
373
-
374
- export def multiply(x, y)
375
- return x * y
376
- end
377
- # => export function multiply(x, y) {
378
- # return x * y
379
- # }
380
-
381
- export default class MyClass
382
- end
383
- # => export default class MyClass {
384
- # };
385
-
386
- # or final export statement:
387
- export [ one, two, default: three ]
388
- # => export { one, two, three as default }
389
- ```
390
-
391
- * <a id="node" href="https://github.com/rubys/ruby2js/blob/master/spec/node_spec.rb">node</a>
392
-
393
- * `` `command` `` becomes `child_process.execSync("command", {encoding: "utf8"})`
394
- * `ARGV` becomes `process.argv.slice(2)`
395
- * `__dir__` becomes `__dirname`
396
- * `Dir.chdir` becomes `process.chdir`
397
- * `Dir.entries` becomes `fs.readdirSync`
398
- * `Dir.mkdir` becomes `fs.mkdirSync`
399
- * `Dir.mktmpdir` becomes `fs.mkdtempSync`
400
- * `Dir.pwd` becomes `process.cwd`
401
- * `Dir.rmdir` becomes `fs.rmdirSync`
402
- * `ENV` becomes `process.env`
403
- * `__FILE__` becomes `__filename`
404
- * `File.chmod` becomes `fs.chmodSync`
405
- * `File.chown` becomes `fs.chownSync`
406
- * `File.cp` becomes `fs.copyFileSync`
407
- * `File.exist?` becomes `fs.existsSync`
408
- * `File.lchmod` becomes `fs.lchmodSync`
409
- * `File.link` becomes `fs.linkSync`
410
- * `File.ln` becomes `fs.linkSync`
411
- * `File.lstat` becomes `fs.lstatSync`
412
- * `File.read` becomes `fs.readFileSync`
413
- * `File.readlink` becomes `fs.readlinkSync`
414
- * `File.realpath` becomes `fs.realpathSync`
415
- * `File.rename` becomes `fs.renameSync`
416
- * `File.stat` becomes `fs.statSync`
417
- * `File.symlink` becomes `fs.symlinkSync`
418
- * `File.truncate` becomes `fs.truncateSync`
419
- * `File.unlink` becomes `fs.unlinkSync`
420
- * `FileUtils.cd` becomes `process.chdir`
421
- * `FileUtils.cp` becomes `fs.copyFileSync`
422
- * `FileUtils.ln` becomes `fs.linkSync`
423
- * `FileUtils.ln_s` becomes `fs.symlinkSync`
424
- * `FileUtils.mkdir` becomes `fs.mkdirSync`
425
- * `FileUtils.mv` becomes `fs.renameSync`
426
- * `FileUtils.pwd` becomes `process.cwd`
427
- * `FileUtils.rm` becomes `fs.unlinkSync`
428
- * `IO.read` becomes `fs.readFileSync`
429
- * `IO.write` becomes `fs.writeFileSync`
430
- * `system` becomes `child_process.execSync(..., {stdio: "inherit"})`
431
-
432
- * <a id="nokogiri" href="https://github.com/rubys/ruby2js/blob/master/spec/nokogiri.rb">nokogiri</a>
433
- * `add_child` becomes `appendChild`
434
- * `add_next_sibling` becomes `node.parentNode.insertBefore(sibling, node.nextSibling)`
435
- * `add_previous_sibling` becomes `node.parentNode.insertBefore(sibling, node)`
436
- * `after` becomes `node.parentNode.insertBefore(sibling, node.nextSibling)`
437
- * `at` becomes `querySelector`
438
- * `attr` becomes `getAttribute`
439
- * `attribute` becomes `getAttributeNode`
440
- * `before` becomes `node.parentNode.insertBefore(sibling, node)`
441
- * `cdata?` becomes `node.nodeType === Node.CDATA_SECTION_NODE`
442
- * `children` becomes `childNodes`
443
- * `comment?` becomes `node.nodeType === Node.COMMENT_NODE`
444
- * `content` becomes `textContent`
445
- * `create_element` becomes `createElement`
446
- * `document` becomes `ownerDocument`
447
- * `element?` becomes `node.nodeType === Node.ELEMENT_NODE`
448
- * `fragment?` becomes `node.nodeType === Node.FRAGMENT_NODE`
449
- * `get_attribute` becomes `getAttribute`
450
- * `has_attribute` becomes `hasAttribute`
451
- * `inner_html` becomes `innerHTML`
452
- * `key?` becomes `hasAttribute`
453
- * `name` becomes `nextSibling`
454
- * `next` becomes `nodeName`
455
- * `next=` becomes `node.parentNode.insertBefore(sibling,node.nextSibling)`
456
- * `next_element` becomes `nextElement`
457
- * `next_sibling` becomes `nextSibling`
458
- * `Nokogiri::HTML5` becomes `new JSDOM().window.document`
459
- * `Nokogiri::HTML5.parse` becomes `new JSDOM().window.document`
460
- * `Nokogiri::HTML` becomes `new JSDOM().window.document`
461
- * `Nokogiri::HTML.parse` becomes `new JSDOM().window.document`
462
- * `Nokogiri::XML::Node.new` becomes `document.createElement()`
463
- * `parent` becomes `parentNode`
464
- * `previous=` becomes `node.parentNode.insertBefore(sibling, node)`
465
- * `previous_element` becomes `previousElement`
466
- * `previous_sibling` becomes `previousSibling`
467
- * `processing_instruction?` becomes `node.nodeType === Node.PROCESSING_INSTRUCTION_NODE`
468
- * `remove_attribute` becomes `removeAttribute`
469
- * `root` becomes `documentElement`
470
- * `search` becomes `querySelectorAll`
471
- * `set_attribute` becomes `setAttribute`
472
- * `text?` becomes `node.nodeType === Node.TEXT_NODE`
473
- * `text` becomes `textContent`
474
- * `to_html` becomes `outerHTML`
475
-
476
- * <a id="underscore" href="https://github.com/rubys/ruby2js/blob/master/spec/underscore.rb">underscore</a>
477
-
478
- * `.clone()` becomes `_.clone()`
479
- * `.compact()` becomes `_.compact()`
480
- * `.count_by {}` becomes `_.countBy {}`
481
- * `.find {}` becomes `_.find {}`
482
- * `.find_by()` becomes `_.findWhere()`
483
- * `.flatten()` becomes `_.flatten()`
484
- * `.group_by {}` becomes `_.groupBy {}`
485
- * `.has_key?()` becomes `_.has()`
486
- * `.index_by {}` becomes `_.indexBy {}`
487
- * `.invert()` becomes `_.invert()`
488
- * `.invoke(&:n)` becomes `_.invoke(, :n)`
489
- * `.map(&:n)` becomes `_.pluck(, :n)`
490
- * `.merge!()` becomes `_.extend()`
491
- * `.merge()` becomes `_.extend({}, )`
492
- * `.reduce {}` becomes `_.reduce {}`
493
- * `.reduce()` becomes `_.reduce()`
494
- * `.reject {}` becomes `_.reject {}`
495
- * `.sample()` becomes `_.sample()`
496
- * `.select {}` becomes `_.select {}`
497
- * `.shuffle()` becomes `_.shuffle()`
498
- * `.size()` becomes `_.size()`
499
- * `.sort()` becomes `_.sort_by(, _.identity)`
500
- * `.sort_by {}` becomes `_.sortBy {}`
501
- * `.times {}` becomes `_.times {}`
502
- * `.values()` becomes `_.values()`
503
- * `.where()` becomes `_.where()`
504
- * `.zip()` becomes `_.zip()`
505
- * `(n...m)` becomes `_.range(n, m)`
506
- * `(n..m)` becomes `_.range(n, m+1)`
507
- * `.compact!`, `.flatten!`, `shuffle!`, `reject!`, `sort_by!`, and
508
- `.uniq` become equivalent `.splice(0, .length, *.method())` statements
509
- * for the following methods, if the block consists entirely of a simple
510
- expression (or ends with one), a `return` is added prior to the
511
- expression: `reduce`, `sort_by`, `group_by`, `index_by`, `count_by`,
512
- `find`, `select`, `reject`.
513
- * `is_a?` and `kind_of?` map to `Object.prototype.toString.call() ===
514
- "[object #{type}]" for the following types: `Arguments`, `Boolean`,
515
- `Date`, `Error`, `Function`, `Number`, `Object`, `RegExp`, `String`; and
516
- maps Ruby names to JavaScript equivalents for `Exception`, `Float`,
517
- `Hash`, `Proc`, and `Regexp`. Additionally, `is_a?` and `kind_of?` map
518
- to `Array.isArray()` for `Array`.
519
-
520
- * <a id="jquery" href="https://github.com/rubys/ruby2js/blob/master/spec/jquery.rb">jquery</a>
521
-
522
- * maps Ruby unary operator `~` to jQuery `$` function
523
- * maps Ruby attribute syntax to jquery attribute syntax
524
- * `.to_a` becomes `toArray`
525
- * maps `$$` to jQuery `$` function
526
- * defaults the fourth parameter of $$.post to `"json"`, allowing Ruby block
527
- syntax to be used for the success function.
528
-
529
- * <a id="minitest-jasmine" href="https://github.com/rubys/ruby2js/blob/master/spec/minitest-jasmine.rb">minitest-jasmine</a>
530
- * maps subclasses of `Minitest::Test` to `describe` calls
531
- * maps `test_` methods inside subclasses of `Minitest::Test` to `it` calls
532
- * maps `setup`, `teardown`, `before`, and `after` calls to `beforeEach`
533
- and `afterEach` calls
534
- * maps `assert` and `refute` calls to `expect`...`toBeTruthy()` and
535
- `toBeFalsy` calls
536
- * maps `assert_equal`, `refute_equal`, `.must_equal` and `.cant_equal`
537
- calls to `expect`...`toBe()` calls
538
- * maps `assert_in_delta`, `refute_in_delta`, `.must_be_within_delta`,
539
- `.must_be_close_to`, `.cant_be_within_delta`, and `.cant_be_close_to`
540
- calls to `expect`...`toBeCloseTo()` calls
541
- * maps `assert_includes`, `refute_includes`, `.must_include`, and
542
- `.cant_include` calls to `expect`...`toContain()` calls
543
- * maps `assert_match`, `refute_match`, `.must_match`, and `.cant_match`
544
- calls to `expect`...`toMatch()` calls
545
- * maps `assert_nil`, `refute_nil`, `.must_be_nil`, and `.cant_be_nill` calls
546
- to `expect`...`toBeNull()` calls
547
- * maps `assert_operator`, `refute_operator`, `.must_be`, and `.cant_be`
548
- calls to `expect`...`toBeGreaterThan()` or `toBeLessThan` calls
549
-
550
- * <a id="cjs" href="https://github.com/rubys/ruby2js/blob/master/spec/cjs">cjs</a>
551
- * maps `export def f` to `exports.f =`
552
- * maps `export async def f` to `exports.f = async`
553
- * maps `export v =` to `exports.v =`
554
- * maps `export default proc` to `module.exports =`
555
- * maps `export default async proc` to `module.exports = async`
556
- * maps `export default` to `module.exports =`
557
-
558
- * <a id="matchAll" href="https://github.com/rubys/ruby2js/blob/master/spec/matchAll">matchAll</a>
559
-
560
- For ES level < 2020:
561
-
562
- * maps `str.matchAll(pattern).forEach {}` to
563
- `while (match = pattern.exec(str)) {}`
564
-
565
- Note `pattern` must be a simple variable with a value of a regular
566
- expression with the `g` flag set at runtime.
567
-
568
- [Wunderbar](https://github.com/rubys/wunderbar) includes additional demos:
569
-
570
- * [chat](https://github.com/rubys/wunderbar/blob/master/demo/chat.rb),
571
- [diskusage](https://github.com/rubys/wunderbar/blob/master/demo/diskusage.rb),
572
- and [wiki](https://github.com/rubys/wunderbar/blob/master/demo/wiki.rb) make
573
- use of the jquery filter.
574
-
575
- ES2015 support
576
- ---
577
-
578
- When option `eslevel: 2015` is provided, the following additional
579
- conversions are made:
580
-
581
- * `"#{a}"` becomes <code>\`${a}\`</code>
582
- * `a = 1` becomes `let a = 1`
583
- * `A = 1` becomes `const A = 1`
584
- * `a, b = b, a` becomes `[a, b] = [b, a]`
585
- * `a, (foo, *bar) = x` becomes `let [a, [foo, ...bar]] = x`
586
- * `def f(a, (foo, *bar))` becomes `function f(a, [foo, ...bar])`
587
- * `def a(b=1)` becomes `function a(b=1)`
588
- * `def a(*b)` becomes `function a(...b)`
589
- * `.each_value` becomes `for (i of ...) {}`
590
- * `a(*b)` becomes `a(...b)`
591
- * `"#{a}"` becomes <code>\`${a}\`</code>
592
- * `lambda {|x| x}` becomes `(x) => {return x}`
593
- * `proc {|x| x}` becomes `(x) => {x}`
594
- * `a {|x|}` becomes `a((x) => {})`
595
- * `class Person; end` becomes `class Person {}`
596
- * `(0...a).to_a` becomes `[...Array(a).keys()]`
597
- * `(0..a).to_a` becomes `[...Array(a+1).keys()]`
598
- * `(b..a).to_a` becomes `Array.from({length: (a-b+1)}, (_, idx) => idx+b)`
599
-
600
- ES2015 class support includes constructors, super, methods, class methods,
601
- instance methods, instance variables, class variables, getters, setters,
602
- attr_accessor, attr_reader, attr_writer, etc.
603
-
604
- Additionally, the `functions` filter will provide the following conversion:
605
-
606
- * `Array(x)` becomes `Array.from(x)`
607
- * `.inject(n) {}` becomes `.reduce(() => {}, n)`
608
-
609
- Finally, keyword arguments and optional keyword arguments will be mapped to
610
- parameter detructuring.
611
-
612
- ES2016 support
613
- ---
614
-
615
- When option `eslevel: 2016` is provided, the following additional
616
- conversion is made:
617
-
618
- * `a ** b` becomes `a ** b`
619
- * `.include?` becomes `.includes`
620
-
621
- ES2017 support
622
- ---
623
-
624
- When option `eslevel: 2017` is provided, the following additional
625
- conversions are made by the `functions` filter:
626
-
627
- * `.values()` becomes `Object.values()`
628
- * `.entries()` becomes `Object.entries()`
629
- * `.each_pair {}` becomes `for (let [key, value] of Object.entries()) {}'
630
-
631
- async support:
632
-
633
- * `async def` becomes `async function`
634
- * `async lambda` becomes `async =>`
635
- * `async proc` becomes `async =>`
636
- * `async ->` becomes `async =>`
637
- * `foo bar, async do...end` becomes `foo(bar, async () => {})`
638
-
639
- ES2018 support
640
- ---
641
-
642
- When option `eslevel: 2018` is provided, the following additional
643
- conversion is made by the `functions` filter:
644
-
645
- * `.merge` becomes `{...a, ...b}`
646
-
647
- Additionally, rest arguments can now be used with keyword arguments and
648
- optional keyword arguments.
649
-
650
- ES2019 support
651
- ---
652
-
653
- When option `eslevel: 2019` is provided, the following additional
654
- conversion is made by the `functions` filter:
655
-
656
- * `.flatten` becomes `.flat(Infinity)`
657
- * `.lstrip` becomes `.trimEnd
658
- * `.rstrip` becomes `.trimStart
659
- * `a.to_h` becomes `Object.fromEntries(a)`
660
- * `Hash[a]` becomes `Object.fromEntries(a)`
661
-
662
- Additionally, `rescue` without a variable will map to `catch` without a
663
- variable.
664
-
665
- ES2020 support
666
- ---
667
-
668
- When option `eslevel: 2020` is provided, the following additional
669
- conversions are made:
670
-
671
- * `@x` becomes `this.#x`
672
- * `@@x` becomes `ClassName.#x`
673
- * `a&.b` becomes `a?.b`
674
- * `.scan` becomes `Array.from(str.matchAll(/.../g), s => s.slice(1))`
675
-
676
- ES2021 support
677
- ---
678
-
679
- When option `eslevel: 2021` is provided, the following additional
680
- conversions are made:
681
-
682
- * `x ||= 1` becomes `x ||= 1`
683
- * `x &&= 1` becomes `x &&= 1`
684
-
685
- Picking a Ruby to JS mapping tool
686
- ---
687
-
688
- > dsl — A domain specific language, where code is written in one language and
689
- > errors are given in another.
690
- > -- [Devil’s Dictionary of Programming](http://programmingisterrible.com/post/65781074112/devils-dictionary-of-programming)
691
-
692
- If you simply want to get a job done, and would like a mature and tested
693
- framework, and only use one of the many integrations that
694
- [Opal](http://opalrb.com/) provides, then Opal is the way to go right now.
695
-
696
- ruby2js is for those that want to produce JavaScript that looks like it
697
- wasn’t machine generated, and want the absolute bare minimum in terms of
698
- limitations as to what JavaScript can be produced.
699
-
700
- [Try](http://intertwingly.net/projects/ruby2js.cgi/all) for yourself.
701
- [Compare](http://opalrb.com/try/#code:).
702
-
703
- And, of course, the right solution might be to use
704
- [CoffeeScript](http://coffeescript.org/) instead.
705
48
 
706
49
  License
707
50
  ---