ruby2js 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -1
  3. data/lib/ruby2js.rb +13 -2
  4. data/lib/ruby2js/converter.rb +31 -0
  5. data/lib/ruby2js/converter/args.rb +1 -1
  6. data/lib/ruby2js/converter/class.rb +6 -4
  7. data/lib/ruby2js/converter/class2.rb +4 -4
  8. data/lib/ruby2js/converter/def.rb +7 -1
  9. data/lib/ruby2js/converter/dstr.rb +3 -1
  10. data/lib/ruby2js/converter/for.rb +1 -1
  11. data/lib/ruby2js/converter/next.rb +10 -2
  12. data/lib/ruby2js/converter/redo.rb +14 -0
  13. data/lib/ruby2js/converter/return.rb +1 -1
  14. data/lib/ruby2js/converter/send.rb +1 -0
  15. data/lib/ruby2js/converter/taglit.rb +9 -2
  16. data/lib/ruby2js/converter/while.rb +1 -1
  17. data/lib/ruby2js/converter/whilepost.rb +1 -1
  18. data/lib/ruby2js/filter/functions.rb +44 -9
  19. data/lib/ruby2js/filter/lit-element.rb +202 -0
  20. data/lib/ruby2js/filter/react.rb +155 -91
  21. data/lib/ruby2js/filter/stimulus.rb +4 -2
  22. data/lib/ruby2js/filter/tagged_templates.rb +4 -2
  23. data/lib/ruby2js/rails.rb +6 -59
  24. data/lib/ruby2js/serializer.rb +16 -11
  25. data/lib/ruby2js/sprockets.rb +40 -0
  26. data/lib/ruby2js/version.rb +2 -2
  27. data/lib/tasks/README.md +4 -0
  28. data/lib/tasks/install/app/javascript/elements/index.js +2 -0
  29. data/lib/tasks/install/config/webpack/loaders/ruby2js.js +20 -0
  30. data/lib/tasks/install/litelement.rb +9 -0
  31. data/lib/tasks/install/preact.rb +3 -0
  32. data/lib/tasks/install/react.rb +2 -0
  33. data/lib/tasks/install/stimulus-sprockets.rb +32 -0
  34. data/lib/tasks/install/stimulus-webpacker.rb +5 -0
  35. data/lib/tasks/install/webpacker.rb +80 -0
  36. data/lib/tasks/nodetest.js +47 -0
  37. data/lib/tasks/ruby2js_tasks.rake +47 -0
  38. data/ruby2js.gemspec +1 -1
  39. metadata +18 -4
@@ -26,8 +26,12 @@ module Ruby2JS
26
26
  extend SEXP
27
27
 
28
28
  REACT_IMPORTS = {
29
- React: s(:import, ['React'], s(:attr, nil, :React)),
30
- ReactDOM: s(:import, ['ReactDOM'], s(:attr, nil, :ReactDOM))
29
+ React: s(:import, ['react'], s(:attr, nil, :React)),
30
+ ReactDOM: s(:import, ['react-dom'], s(:attr, nil, :ReactDOM)),
31
+ Preact: s(:import,
32
+ [s(:pair, s(:sym, :as), s(:const, nil, :Preact)),
33
+ s(:pair, s(:sym, :from), s(:str, "preact"))],
34
+ s(:str, '*'))
31
35
  }
32
36
 
33
37
  # the following command can be used to generate ReactAttrs:
@@ -75,7 +79,12 @@ module Ruby2JS
75
79
 
76
80
  ReactAttrMap = Hash[ReactAttrs.map {|name| [name.downcase, name]}]
77
81
  ReactAttrMap['for'] = 'htmlFor'
78
- ReactFragment = :'_React.Fragment'
82
+
83
+ PreactAttrMap = {
84
+ htmlFor: 'for',
85
+ onDoubleClick: 'onDblClick',
86
+ tabIndex: 'tabindex'
87
+ }
79
88
 
80
89
  def initialize(*args)
81
90
  @react = nil
@@ -85,7 +94,6 @@ module Ruby2JS
85
94
  @react_props = []
86
95
  @react_methods = []
87
96
  @react_filter_functions = false
88
- @react_imports = false
89
97
  @jsx = false
90
98
  super
91
99
  end
@@ -102,15 +110,6 @@ module Ruby2JS
102
110
  @react_filter_functions = true
103
111
  end
104
112
 
105
- if \
106
- (defined? Ruby2JS::Filter::ESM and
107
- filters.include? Ruby2JS::Filter::ESM) or
108
- (defined? Ruby2JS::Filter::CJS and
109
- filters.include? Ruby2JS::Filter::CJS)
110
- then
111
- @react_imports = true
112
- end
113
-
114
113
  if \
115
114
  defined? Ruby2JS::Filter::JSX and
116
115
  filters.include? Ruby2JS::Filter::JSX
@@ -128,12 +127,23 @@ module Ruby2JS
128
127
  def on_class(node)
129
128
  cname, inheritance, *body = node.children
130
129
  return super unless cname.children.first == nil
131
- return super unless inheritance == s(:const, nil, :React) or
132
- inheritance == s(:const, nil, :Vue) or
130
+
131
+ if inheritance == s(:const, nil, :React) or
133
132
  inheritance == s(:const, s(:const, nil, :React), :Component) or
134
133
  inheritance == s(:send, s(:const, nil, :React), :Component)
135
134
 
136
- prepend_list << REACT_IMPORTS[:React] if @react_imports
135
+ react = :React
136
+ prepend_list << REACT_IMPORTS[:React] if modules_enabled?
137
+
138
+ elsif inheritance == s(:const, nil, :Preact) or
139
+ inheritance == s(:const, s(:const, nil, :Preact), :Component) or
140
+ inheritance == s(:send, s(:const, nil, :Preact), :Component)
141
+
142
+ react = :Preact
143
+ prepend_list << REACT_IMPORTS[:Preact] if modules_enabled?
144
+ else
145
+ return super
146
+ end
137
147
 
138
148
  # traverse down to actual list of class statements
139
149
  if body.length == 1
@@ -151,12 +161,14 @@ module Ruby2JS
151
161
  end
152
162
 
153
163
  begin
154
- react, @react = @react, true
164
+ react, @react = @react, react
155
165
  reactClass, @reactClass = @reactClass, true
156
166
 
157
167
  pairs = []
158
168
 
159
- unless es2015
169
+ createClass = (@react == :React and not es2015)
170
+
171
+ if createClass
160
172
  # automatically capture the displayName for the class
161
173
  pairs << s(:pair, s(:sym, :displayName),
162
174
  s(:str, cname.children.last.to_s))
@@ -166,9 +178,8 @@ module Ruby2JS
166
178
  statics = []
167
179
  body.select {|child| child.type == :defs}.each do |child|
168
180
  _parent, mname, args, *block = child.children
169
- if es2015
170
- block = [s(:autoreturn, *block)] unless child.is_method?
171
- pairs << s(:defs, s(:self), mname, args, *block)
181
+ if not createClass
182
+ pairs << child
172
183
  elsif child.is_method?
173
184
  statics << s(:pair, s(:sym, mname), process(child.updated(:block,
174
185
  [s(:send, nil, :proc), args, s(:autoreturn, *block)])))
@@ -222,7 +233,7 @@ module Ruby2JS
222
233
  scan_events[node.children]
223
234
  end
224
235
  end
225
- scan_events[body] unless es2015
236
+ scan_events[body] if createClass
226
237
 
227
238
  # append statics (if any)
228
239
  unless statics.empty?
@@ -240,7 +251,7 @@ module Ruby2JS
240
251
  then
241
252
  @reactIvars = {pre: [], post: [], asgn: [], ref: [], cond: []}
242
253
  react_walk(node)
243
- if not es2015 and not @reactIvars.values.flatten.empty?
254
+ if createClass and not @reactIvars.values.flatten.empty?
244
255
  body = [s(:def, :getInitialState, s(:args),
245
256
  s(:return, s(:hash))), *body]
246
257
  elsif not needs_binding.empty? or not @reactIvars.values.flatten.empty?
@@ -261,7 +272,7 @@ module Ruby2JS
261
272
  (@reactIvars[:pre] + @reactIvars[:post]).uniq
262
273
 
263
274
  if mname == :initialize
264
- mname = es2015 ? :initialize : :getInitialState
275
+ mname = createClass ? :getInitialState : :initialize
265
276
 
266
277
  # extract real list of statements
267
278
  if block.length == 1
@@ -343,7 +354,7 @@ module Ruby2JS
343
354
  else
344
355
  # wrap multi-line blocks with a React Fragment
345
356
  block = [s(:return,
346
- s(:block, s(:send, nil, ReactFragment), s(:args), *block))]
357
+ s(:block, s(:send, nil, :"_#{@react}.Fragment"), s(:args), *block))]
347
358
  end
348
359
  end
349
360
 
@@ -369,14 +380,14 @@ module Ruby2JS
369
380
  type = :begin if block.first.type == :return
370
381
  end
371
382
 
372
- if es2015
383
+ if createClass
384
+ pairs << s(:pair, s(:sym, mname), child.updated(:block,
385
+ [s(:send, nil, :proc), args, process(s(type, *block))]))
386
+ else
373
387
  pairs << child.updated(
374
388
  ReactLifecycle.include?(mname.to_s) ? :defm : :def,
375
389
  [mname, args, process(s(type, *block))]
376
390
  )
377
- else
378
- pairs << s(:pair, s(:sym, mname), child.updated(:block,
379
- [s(:send, nil, :proc), args, process(s(type, *block))]))
380
391
  end
381
392
 
382
393
  # retain comment
@@ -384,38 +395,24 @@ module Ruby2JS
384
395
  @comments[pairs.last] = @comments[child]
385
396
  end
386
397
  end
398
+
399
+ if createClass
400
+ # emit a createClass statement
401
+ node.updated(:casgn, [nil, cname.children.last,
402
+ s(:send, s(:const, nil, :React), :createClass, s(:hash, *pairs))])
403
+ else
404
+ # emit a class that extends React.Component
405
+ node.updated(:class, [s(:const, nil, cname.children.last),
406
+ s(:attr, s(:const, nil, @react), :Component), *pairs])
407
+ end
387
408
  ensure
388
409
  @react = react
389
410
  @reactClass = reactClass
390
411
  @reactMethod = nil
391
412
  end
392
-
393
- if es2015
394
- # emit a class that extends React.Component
395
- node.updated(:class, [s(:const, nil, cname.children.last),
396
- s(:attr, s(:const, nil, :React), :Component), *pairs])
397
- else
398
- # emit a createClass statement
399
- node.updated(:casgn, [nil, cname.children.last,
400
- s(:send, s(:const, nil, :React), :createClass, s(:hash, *pairs))])
401
- end
402
413
  end
403
414
 
404
415
  def on_send(node)
405
- # convert Vue.utile.defineReactive to class fields or assignments
406
- if node.children.first == s(:send, s(:const, nil, :Vue), :util)
407
- if node.children[1] == :defineReactive
408
- if node.children[2].type == :cvar
409
- return process s(:cvasgn, node.children[2].children.first,
410
- node.children[3])
411
- elsif node.children[2].type == :send
412
- assign = node.children[2]
413
- return assign.updated(nil, [assign.children[0],
414
- assign.children[1].to_s + '=', node.children[3]])
415
- end
416
- end
417
- end
418
-
419
416
  # calls to methods (including getters) defined in this class
420
417
  if node.children[0]==nil and Symbol === node.children[1]
421
418
  if node.is_method?
@@ -433,24 +430,21 @@ module Ruby2JS
433
430
  end
434
431
  end
435
432
 
436
- if node.children.first == s(:const, nil, :Vue)
437
- node = node.updated(nil, [s(:const, nil, :React),
438
- *node.children[1..-1]])
439
- end
440
-
441
433
  if not @react
442
434
  # enable React filtering within React class method calls or
443
435
  # React component calls
444
436
  if \
445
437
  node.children.first == s(:const, nil, :React) or
438
+ node.children.first == s(:const, nil, :Preact) or
446
439
  node.children.first == s(:const, nil, :ReactDOM)
447
440
  then
448
- if @react_imports
441
+ if modules_enabled?
449
442
  prepend_list << REACT_IMPORTS[node.children.first.children.last]
450
443
  end
451
444
 
452
445
  begin
453
- react, @react = @react, true
446
+ react = @react
447
+ @react = (node.children.first.children.last == :Preact ? :Preact : :React)
454
448
  return on_send(node)
455
449
  ensure
456
450
  @react = react
@@ -471,13 +465,26 @@ module Ruby2JS
471
465
  end
472
466
 
473
467
  elsif \
474
- @reactApply and node.children[1] == :createElement and
475
- node.children[0] == s(:const, nil, :React)
468
+ (@reactApply and node.children[1] == :createElement and
469
+ node.children[0] == s(:const, nil, :React)) or
470
+ (@reactApply and node.children[1] == :h and
471
+ node.children[0] == s(:const, nil, :Preact))
476
472
  then
477
473
  # push results of explicit calls to React.createElement
478
474
  s(:send, s(:gvar, :$_), :push, s(:send, *node.children[0..1],
479
475
  *process_all(node.children[2..-1])))
480
476
 
477
+ elsif \
478
+ @react == :Preact and node.children[1] == :h and node.children[0] == nil
479
+ then
480
+ if @reactApply
481
+ # push results of explicit calls to Preact.h
482
+ s(:send, s(:gvar, :$_), :push, s(:send, s(:const, nil, :Preact), :h,
483
+ *process_all(node.children[2..-1])))
484
+ else
485
+ node.updated(nil, [s(:const, nil, :Preact), :h, *process_all(node.children[2..-1])])
486
+ end
487
+
481
488
  elsif !@jsx and node.children[0] == nil and node.children[1] =~ /^_\w/
482
489
  # map method calls starting with an underscore to React calls
483
490
  # to create an element.
@@ -559,7 +566,12 @@ module Ruby2JS
559
566
  else
560
567
  value = s(:str, values.join(' '))
561
568
  end
562
- pairs.unshift s(:pair, s(:sym, :className), value)
569
+
570
+ if @react == :Preact
571
+ pairs.unshift s(:pair, s(:sym, :class), value)
572
+ else
573
+ pairs.unshift s(:pair, s(:sym, :className), value)
574
+ end
563
575
  end
564
576
 
565
577
  # support controlled form components
@@ -569,13 +581,28 @@ module Ruby2JS
569
581
  ['value', :value].include? pair.children.first.children.first
570
582
  end
571
583
 
572
- # search for the presence of a 'onChange' attribute
584
+ event = (@react == :Preact ? :onInput : :onChange)
585
+
586
+
587
+ # search for the presence of a onInput/onChange attribute
573
588
  onChange = pairs.find_index do |pair|
574
- ['onChange', :onChange].include? pair.children.first.children[0]
589
+ pair.children.first.children[0].to_s == event.to_s
590
+ end
591
+
592
+ if event == :onInput and not onChange
593
+ # search for the presence of a 'onChange' attribute
594
+ onChange = pairs.find_index do |pair|
595
+ pair.children.first.children[0].to_s == 'onChange'
596
+ end
597
+
598
+ if onChange
599
+ pairs[onChange] = s(:pair, s(:sym, event),
600
+ pairs[onChange].children.last)
601
+ end
575
602
  end
576
603
 
577
604
  if value and pairs[value].children.last.type == :ivar and !onChange
578
- pairs << s(:pair, s(:sym, :onChange),
605
+ pairs << s(:pair, s(:sym, event),
579
606
  s(:block, s(:send, nil, :proc), s(:args, s(:arg, :event)),
580
607
  s(:ivasgn, pairs[value].children.last.children.first,
581
608
  s(:attr, s(:attr, s(:lvar, :event), :target), :value))))
@@ -588,7 +615,7 @@ module Ruby2JS
588
615
  end
589
616
 
590
617
  if checked and pairs[checked].children.last.type == :ivar
591
- pairs << s(:pair, s(:sym, :onChange),
618
+ pairs << s(:pair, s(:sym, event),
592
619
  s(:block, s(:send, nil, :proc), s(:args),
593
620
  s(:ivasgn, pairs[checked].children.last.children.first,
594
621
  s(:send, pairs[checked].children.last, :!))))
@@ -596,13 +623,25 @@ module Ruby2JS
596
623
  end
597
624
  end
598
625
 
599
- # replace attribute names with case-sensitive javascript properties
600
- pairs.each_with_index do |pair, index|
601
- next if pair.type == :kwsplat
602
- name = pair.children.first.children.first.downcase
603
- if ReactAttrMap[name] and name.to_s != ReactAttrMap[name]
604
- pairs[index] = pairs[index].updated(nil,
605
- [s(:str, ReactAttrMap[name]), pairs[index].children.last])
626
+ if @react == :Preact
627
+ # replace selected Reactisms with native HTML
628
+ pairs.each_with_index do |pair, index|
629
+ next if pair.type == :kwsplat
630
+ name = pair.children.first.children.first.to_sym
631
+ if PreactAttrMap[name]
632
+ pairs[index] = pairs[index].updated(nil,
633
+ [s(:str, PreactAttrMap[name]), pairs[index].children.last])
634
+ end
635
+ end
636
+ else
637
+ # replace attribute names with case-sensitive javascript properties
638
+ pairs.each_with_index do |pair, index|
639
+ next if pair.type == :kwsplat
640
+ name = pair.children.first.children.first.downcase
641
+ if ReactAttrMap[name] and name.to_s != ReactAttrMap[name]
642
+ pairs[index] = pairs[index].updated(nil,
643
+ [s(:str, ReactAttrMap[name]), pairs[index].children.last])
644
+ end
606
645
  end
607
646
  end
608
647
 
@@ -664,8 +703,14 @@ module Ruby2JS
664
703
  # explicit call to React.createElement
665
704
  next true if arg.children[1] == :createElement and
666
705
  arg.children[0] == s(:const, nil, :React)
667
- next true if arg.children[1] == :createElement and
668
- arg.children[0] == s(:const, nil, :Vue)
706
+
707
+ # explicit call to Preact.h
708
+ next true if arg.children[1] == :h and
709
+ arg.children[0] == s(:const, nil, :Preact)
710
+
711
+ # explicit call to h
712
+ next true if arg.children[1] == :h and
713
+ arg.children[0] == nil
669
714
 
670
715
  # JSX
671
716
  next true if arg.type == :xstr
@@ -727,8 +772,13 @@ module Ruby2JS
727
772
  params.pop if params.last == s(:nil)
728
773
 
729
774
  # construct element using params
730
- element = node.updated(:send, [s(:const, nil, :React),
731
- :createElement, *params])
775
+ if @react == :Preact
776
+ element = node.updated(:send, [s(:const, nil, :Preact),
777
+ :h, *params])
778
+ else
779
+ element = node.updated(:send, [s(:const, nil, :React),
780
+ :createElement, *params])
781
+ end
732
782
 
733
783
  if @reactApply
734
784
  # if apply is set, emit code that pushes result
@@ -856,8 +906,13 @@ module Ruby2JS
856
906
  while node != child
857
907
  if node.children[1] !~ /!$/
858
908
  # convert method name to hash {className: name} pair
859
- pair = s(:pair, s(:sym, :className),
860
- s(:str, node.children[1].to_s.gsub('_','-')))
909
+ if @react == :Preact
910
+ pair = s(:pair, s(:sym, :class),
911
+ s(:str, node.children[1].to_s.gsub('_','-')))
912
+ else
913
+ pair = s(:pair, s(:sym, :className),
914
+ s(:str, node.children[1].to_s.gsub('_','-')))
915
+ end
861
916
  else
862
917
  # convert method name to hash {id: name} pair
863
918
  pair = s(:pair, s(:sym, :id),
@@ -927,8 +982,11 @@ module Ruby2JS
927
982
  # Base Ruby2JS processing will convert the 'splat' to 'apply'
928
983
  child = node.children.first
929
984
  if \
930
- child.children[1] == :createElement and
931
- child.children[0] == s(:const, nil, :React)
985
+ (child.children[1] == :createElement and
986
+ child.children[0] == s(:const, nil, :React)) or
987
+ (child.children[1] == :h and
988
+ (child.children[0] == s(:const, nil, :Preact) or
989
+ child.children[0] == nil))
932
990
  then
933
991
  begin
934
992
  reactApply, @reactApply = @reactApply, true
@@ -941,11 +999,13 @@ module Ruby2JS
941
999
  @reactApply = reactApply
942
1000
  end
943
1001
 
1002
+ target = child.children[0] || s(:const, nil, :Preact)
1003
+
944
1004
  if reactApply
945
1005
  return child.updated(:send, [s(:gvar, :$_), :push,
946
- s(:send, *child.children[0..1], *params)])
1006
+ s(:send, target, child.children[1], *params)])
947
1007
  else
948
- return child.updated(:send, [*child.children[0..1], *params])
1008
+ return child.updated(:send, [target, child.children[1], *params])
949
1009
  end
950
1010
  end
951
1011
 
@@ -962,7 +1022,7 @@ module Ruby2JS
962
1022
  block = s(:block, s(:send, nil, :proc), s(:args),
963
1023
  *node.children[2..-1])
964
1024
  return on_send node.children.first.updated(:send,
965
- [nil, ReactFragment, block])
1025
+ [nil, :"_#{@react}.Fragment", block])
966
1026
 
967
1027
  elsif !@jsx and child.children[0] == nil and child.children[1] =~ /^_\w/
968
1028
  if node.children[1].children.empty?
@@ -1030,7 +1090,7 @@ module Ruby2JS
1030
1090
  ivar = node.children.first.to_s
1031
1091
  if @reactBlock
1032
1092
  return s(:send, s(:self), :setState, s(:hash, s(:pair,
1033
- s(:lvar, ivar[1..-1]), process(s(:lvasgn, "$#{ivar[1..-1]}",
1093
+ s(:str, ivar[1..-1]), process(s(:lvasgn, "$#{ivar[1..-1]}",
1034
1094
  *node.children[1..-1])))))
1035
1095
  else
1036
1096
  return s(:lvasgn, "$#{ivar[1..-1]}",
@@ -1085,7 +1145,7 @@ module Ruby2JS
1085
1145
  if @reactMethod and @reactIvars[:capture].include? var
1086
1146
  if @reactBlock
1087
1147
  s(:send, s(:self), :setState, s(:hash, s(:pair,
1088
- s(:lvar, var[1..-1]), process(s(node.type,
1148
+ s(:str, var[1..-1]), process(s(node.type,
1089
1149
  s(:lvasgn, "$#{var[1..-1]}"), *node.children[1..-1])))))
1090
1150
  else
1091
1151
  process s(node.type, s(:lvasgn, "$#{var[1..-1]}"),
@@ -1128,9 +1188,13 @@ module Ruby2JS
1128
1188
  return true if node.children[1] == :createElement and
1129
1189
  node.children[0] == s(:const, nil, :React)
1130
1190
 
1131
- # explicit call to Vue.createElement
1132
- return true if node.children[1] == :createElement and
1133
- node.children[0] == s(:const, nil, :Vue)
1191
+ # explicit call to Preact.h
1192
+ return true if node.children[1] == :h and
1193
+ node.children[0] == s(:const, nil, :Preact)
1194
+
1195
+ # explicit call to h
1196
+ return true if node.children[1] == :h and
1197
+ node.children[0] == nil
1134
1198
  end
1135
1199
 
1136
1200
  # wunderbar style call
@@ -1290,7 +1354,7 @@ module Ruby2JS
1290
1354
  # update ivars that are set and later referenced
1291
1355
  unless @reactIvars[:post].empty?
1292
1356
  updates = @reactIvars[:post].uniq.sort.reverse.map do |ivar|
1293
- s(:pair, s(:lvar, ivar.to_s[1..-1]),
1357
+ s(:pair, s(:str, ivar.to_s[1..-1]),
1294
1358
  s(:lvar, "$#{ivar.to_s[1..-1]}"))
1295
1359
  end
1296
1360
  update = s(:send, s(:self), :setState, s(:hash, *updates))