ruby2js 4.0.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
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))