opal_stimulus 0.1.3 → 0.1.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 123ac1d5e6112d3896cd623518b80732351357cfa11443dd8142fd07f32bec49
4
- data.tar.gz: 41bbd48c083fc12359f979f8c8c5d5de2486acd56cf969c0af417edf9576c1d0
3
+ metadata.gz: f328bd466f89d10419369095e006b923c4a389c58fa14abd7c57ae2c57fb804d
4
+ data.tar.gz: 7b6a16a7adc502cdedcdad7e0e71f152652022a42d5a674fc033f6f13e514660
5
5
  SHA512:
6
- metadata.gz: fa36ee6800f4e0795640c29c061be9b5c49cdc639c9b976381af2ea41863a40a22cac80368956edb1ca569712191f016764d4c4fa16ceb9583f5fca164b391f5
7
- data.tar.gz: 9cea24bad6cbf6bfba34ab79026b8589b6f25c180ee210f263568e275a91a1da16314539ecda837e222637657e4534a4d8a421ec8e38aa9c6a90b1c67cca1605
6
+ metadata.gz: 775c06738928c9c1584d0515064cf7e5984ff1259cb7def3c56cd9bc34942a7da50a39a761400d9f80a824a178d3333887fedfcfec4274adfa261a3819614d59
7
+ data.tar.gz: f3da082b6361dc0281e743a7ded76c953504c044abe2fc5b59febbd2d68e65138bb4eea4ef2acaab08f8a3a3073c655c3e325c0084f15bce58bd8d23f30d0b2e
data/.rspec-opal ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/CHANGELOG.md CHANGED
@@ -16,3 +16,19 @@
16
16
 
17
17
  - Add missing ignore for app/assets/builds/opal.js.map
18
18
  - Replace opal-browser with opal_proxy, check it out here.
19
+
20
+ ## [0.1.4] - 2025-08-03
21
+
22
+ - Fix Targets
23
+ - Fix Outlets
24
+ - Fix Values
25
+ - Fix `element`
26
+ - Fix Classes
27
+ - Add `document` and `window` private methods
28
+
29
+ ## [0.1.5] - 2025-08-14
30
+
31
+ - Add `StimulusController` unit and system tests
32
+ - Reduce installed new lines for Rails
33
+ - Skip importmap in production
34
+ - Add `bin/rails generate opal_stimulus <CONTROLLER_NAME>`, usage: `bin/rails g opal_stimulus -h`
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  **Opal Stimulus** is a Stimulus wrapper made with Opal (a source-to-source Ruby to JavaScript compiler) that allows you to write Stimulus controllers in Ruby instead of JavaScript (It works only with Rails).
4
4
 
5
+ [![Ruby](https://github.com/josephschito/opal_stimulus/actions/workflows/main.yml/badge.svg)](https://github.com/josephschito/opal_stimulus/actions/workflows/main.yml)
6
+
5
7
  ## Installation
6
8
 
7
9
  Add this line to your Gemfile:
@@ -23,6 +25,13 @@ Start application:
23
25
  bin/dev
24
26
  ```
25
27
 
28
+ Create a new controller:
29
+
30
+ ```bash
31
+ bin/rails opal_stimulus hello
32
+ ```
33
+ This command will create `app/opal/controllers/hello_controller.rb`
34
+
26
35
  ## Basic Example
27
36
 
28
37
  Here's a Hello World example with OpalStimulus. Compare with the [original JavaScript example](https://stimulus.hotwired.dev/#:~:text=%2F%2F%20hello_controller.js%0Aimport%20%7B%20Controller%20%7D%20from%20%22stimulus%22%0A%0Aexport%20default%20class%20extends%20Controller%20%7B%0A%20%20static%20targets%20%3D%20%5B%20%22name%22%2C%20%22output%22%20%5D%0A%0A%20%20greet()%20%7B%0A%20%20%20%20this.outputTarget.textContent%20%3D%0A%20%20%20%20%20%20%60Hello%2C%20%24%7Bthis.nameTarget.value%7D!%60%0A%20%20%7D%0A%7D):
@@ -31,8 +40,6 @@ Here's a Hello World example with OpalStimulus. Compare with the [original JavaS
31
40
 
32
41
  ```ruby
33
42
  # app/opal/controllers/hello_controller.rb
34
- # new controllers will be automatically added to app/opal/controllers_requires.rb
35
- # (ordered files load is not supported yet)
36
43
  class HelloController < StimulusController
37
44
  self.targets = ["name", "output"]
38
45
 
@@ -61,8 +68,125 @@ end
61
68
 
62
69
  https://github.com/user-attachments/assets/c51ed28c-13d2-4e06-b882-1cc997e9627b
63
70
 
71
+ **Notes:**
72
+
73
+ - In general any reference method is snake cased, so `container` target will produce `container_target` and not ~`containerTarget`~
74
+ - Any `target`, `element`, `document`, `window` and `event` is a `JS::Proxy` instance, that provides a dynamic interface to JavaScript objects in Opal
75
+ - The frontend definition part will remain the same
76
+
77
+
78
+ ## Some examples based on [Stimulus Reference](https://stimulus.hotwired.dev/reference/controllers)
79
+
80
+ ### Lifecycle Callbacks
81
+ ```ruby
82
+ class AlertController < StimulusController
83
+ def initialize; end
84
+ def connect; end
85
+ def disconnect; end
86
+ end
87
+ ```
88
+
89
+ ### Actions
90
+ ```ruby
91
+ class WindowResizeController < StimulusController
92
+ def resized(event)
93
+ if !@resized && event.target.inner_width >= 1080
94
+ puts "Full HD? Nice!"
95
+ @resized = true
96
+ end
97
+ end
98
+ end
99
+ ```
100
+
101
+ ### Targets
102
+ ```ruby
103
+ class ContainerController < StimulusController
104
+ self.targets = ["container"]
105
+
106
+ def container_target_connected
107
+ container_target.inner_html = <<~HTML
108
+ <h1>Test connected!</h1>
109
+ HTML
110
+ end
111
+
112
+ def container_target_disconnected
113
+ puts "Container disconnected!"
114
+ end
115
+ end
116
+ ```
117
+
118
+ ### Outlets
119
+ ```ruby
120
+ class ChatController < StimulusController
121
+ self.outlets = [ "user-status" ]
122
+
123
+ def connect
124
+ user_status_outlets.each do |status|
125
+ puts status
126
+ end
127
+ end
128
+ end
129
+ ```
130
+
131
+ ### Values
132
+ ```ruby
133
+ class LoaderController < StimulusController
134
+ self.values = { url: :string }
135
+
136
+ def connect
137
+ window.fetch(url_value).then do |response|
138
+ response.json().then do |data|
139
+ load_data(data)
140
+ end
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def load_data(data)
147
+ # ...
148
+ end
149
+ end
150
+ ```
151
+
152
+ ### CSS Classes
153
+ ```ruby
154
+ class SearchController < StimulusController
155
+ self.classes = [ "loading" ]
64
156
 
157
+ def loadResults
158
+ element.class_list.add loading_class
159
+ end
160
+ end
161
+ ```
162
+
163
+ ## Extra tools
164
+ ### Window
165
+ ```ruby
166
+ class WindowController < StimulusController
167
+ def connect
168
+ window.alert "Hello world!"
169
+ window.set_timeout(-> {
170
+ puts "1. Timeout test OK (1s delay)"
171
+ }, 1000)
172
+ end
173
+ end
174
+ ```
175
+
176
+ ### Document
177
+ ```ruby
178
+ class DocumentController < StimulusController
179
+ def connect
180
+ headers = document.querySelectorAll("h1")
181
+ headers.each do |h1|
182
+ h1.text_content = "Opal is great!"
183
+ end
184
+ end
185
+ end
186
+ ```
65
187
 
188
+ ## Run tests
189
+ `bundle exec rake`
66
190
 
67
191
  ## Contributing
68
192
 
data/Rakefile CHANGED
@@ -9,4 +9,15 @@ require "rubocop/rake_task"
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
12
- task default: %i[test rubocop]
12
+ require 'opal/rspec/rake_task'
13
+ require "opal"
14
+
15
+ Opal.use_gem("opal_proxy")
16
+ Opal.append_path File.expand_path('../lib', __FILE__)
17
+ Opal.append_path File.expand_path('../shared_fixtures', __FILE__)
18
+
19
+ Opal::RSpec::RakeTask.new("rspec-opal") do |server, task|
20
+ task.runner = :chrome
21
+ end
22
+
23
+ task default: %i[test rspec-opal]
@@ -0,0 +1,14 @@
1
+ Description:
2
+ ============
3
+ Generates a new Opal Stimulus controller at the passed path.
4
+
5
+ Examples:
6
+ =========
7
+ bin/rails generate opal_stimulus chat
8
+
9
+ creates: app/opal/controllers/chat_controller.rb
10
+
11
+
12
+ bin/rails generate stimulus nested/chat
13
+
14
+ creates: app/opal/controllers/nested/chat_controller.rb
@@ -0,0 +1,9 @@
1
+ require "rails/generators/named_base"
2
+
3
+ class OpalStimulusGenerator < Rails::Generators::NamedBase
4
+ source_root File.expand_path("templates", __dir__)
5
+
6
+ def create_controller_file
7
+ template "controller.rb.tt", File.join("app/opal/controllers", class_path, "#{file_name}_controller.rb")
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ # Connects to data-controller="<%= file_name.dasherize %>"
2
+ <% constants = class_name.split("::") -%>
3
+ <% modules = constants[0..-2] -%>
4
+ <% indentation = " " * (modules.length) -%>
5
+ <% modules.each do |constant| -%>
6
+ module <%= constant %>
7
+ <% end -%>
8
+ <%= indentation %>class <%= constants.last %>Controller < StimulusController
9
+ <%= indentation %> def connect
10
+ <%= indentation %> end
11
+ <%= indentation %>end
12
+ <% modules.each.with_index do |constant, i| -%>
13
+ <%= " " * i %>end
14
+ <% end -%>
@@ -1,2 +1,2 @@
1
1
  web: bin/rails server
2
- opal: bin/opal --watch
2
+ opal: bin/rails opal_stimulus:watch
@@ -1,9 +1,4 @@
1
1
  require "opal_stimulus/stimulus_controller"
2
- require "controllers_requires"
2
+ require_tree "controllers"
3
3
 
4
- StimulusController.subclasses.each do |controller|
5
- controller.define_method(:dummy) { }
6
-
7
- return if `Stimulus.controllers`.include?(`#{controller.stimulus_name}`)
8
- `Stimulus.register(#{controller.stimulus_name}, #{controller.stimulus_controller})`
9
- end
4
+ StimulusController.register_all!
@@ -13,16 +13,9 @@ if File.exist? APPLICATION_LAYOUT_PATH
13
13
  ERB
14
14
  end
15
15
 
16
- insert_into_file APPLICATION_LAYOUT_PATH, after: "<body>\n" do
17
- say "Adding `my-opal` to the application layout", :green
18
- <<-ERB
19
- <span data-controller="my-opal"></span>
20
- ERB
21
- end
22
-
23
16
  say "Creating Opal Stimulus files", :green
24
17
  if Rails.root.join("Procfile.dev").exist?
25
- append_to_file "Procfile.dev", "opal: bin/opal --watch\n"
18
+ append_to_file "Procfile.dev", "opal: bin/rails opal_stimulus:watch\n"
26
19
  else
27
20
  say "Add default Procfile.dev"
28
21
  copy_file "#{__dir__}/Procfile.dev", "Procfile.dev"
@@ -44,13 +37,9 @@ window.Controller = Controller;
44
37
  empty_directory APPLICATION_OPAL_STIMULUS_BIN_PATH
45
38
  empty_directory APPLICATION_OPAL_STIMULUS_PATH
46
39
  empty_directory "#{APPLICATION_OPAL_STIMULUS_PATH}/controllers"
47
- empty_directory "#{APPLICATION_OPAL_STIMULUS_PATH}/app/assets/builds"
40
+ create_file "#{APPLICATION_OPAL_STIMULUS_PATH}/controllers/.keep"
48
41
  create_file "app/assets/builds/.keep"
49
- copy_file "#{__dir__}/opal", "#{APPLICATION_OPAL_STIMULUS_BIN_PATH}/opal"
50
- FileUtils.chmod("+x", "#{APPLICATION_OPAL_STIMULUS_BIN_PATH}/opal")
51
42
  copy_file "#{__dir__}/dev", "#{APPLICATION_OPAL_STIMULUS_BIN_PATH}/dev"
52
43
  FileUtils.chmod("+x", "#{APPLICATION_OPAL_STIMULUS_BIN_PATH}/dev")
53
44
  copy_file "#{__dir__}/application.rb", "#{APPLICATION_OPAL_STIMULUS_PATH}/application.rb"
54
- copy_file "#{__dir__}/controllers_requires.rb", "#{APPLICATION_OPAL_STIMULUS_PATH}/controllers_requires.rb"
55
- copy_file "#{__dir__}/controllers/my_opal_controller.rb", "#{APPLICATION_OPAL_STIMULUS_PATH}/controllers/my_opal_controller.rb"
56
45
  end
@@ -5,8 +5,6 @@ require "native"
5
5
  require "js/proxy"
6
6
 
7
7
  class StimulusController < `Controller`
8
- include Native::Wrapper
9
-
10
8
  DEFAULT_METHODS = %i[initialize connect disconnect dispatch]
11
9
  DEFAULT_GETTERS = %i[element]
12
10
 
@@ -30,7 +28,13 @@ class StimulusController < `Controller`
30
28
  %x{
31
29
  #{self.stimulus_controller}.prototype[name] = function (...args) {
32
30
  try {
33
- return this['$' + name].apply(this, args);
31
+ var wrappedArgs = args.map(function(arg) {
32
+ if (arg && typeof arg === "object" && !arg.$$class) {
33
+ return Opal.JS.Proxy.$new(arg);
34
+ }
35
+ return arg;
36
+ });
37
+ return this['$' + name].apply(this, wrappedArgs);
34
38
  } catch (e) {
35
39
  console.error("Uncaught", e);
36
40
  }
@@ -38,26 +42,45 @@ class StimulusController < `Controller`
38
42
  }
39
43
  end
40
44
 
45
+ def self.to_ruby_name(name)
46
+ name
47
+ .to_s
48
+ .gsub(/([A-Z]+)/) { "_#{$1.downcase}" }
49
+ .sub(/^_/, '')
50
+ end
51
+
52
+ def self.register_all!
53
+ subclasses.each do |controller|
54
+ controller.define_method(:dummy) {}
55
+
56
+ return if `application.controllers`.include?(`#{controller.stimulus_name}`)
57
+ `application.register(#{controller.stimulus_name}, #{controller.stimulus_controller})`
58
+ end
59
+ end
60
+
41
61
  def self.targets=(targets = [])
42
62
  `#{self.stimulus_controller}.targets = targets`
43
63
 
44
64
  targets.each do |target|
45
- define_method(target + "_target") do
46
- JS::Proxy.new(`this[#{target + "Target"}]`)
65
+ js_name = target.to_s
66
+ ruby_name = self.to_ruby_name(target)
67
+
68
+ define_method(ruby_name + "_target") do
69
+ JS::Proxy.new(`this[#{js_name + "Target"}]`)
47
70
  end
48
71
 
49
- define_method(target + "_targets") do
50
- `this[#{target + "Targets"}]`.map do |el|
72
+ define_method(ruby_name + "_targets") do
73
+ `this[#{js_name + "Targets"}]`.map do |el|
51
74
  JS::Proxy.new(el)
52
75
  end
53
76
  end
54
77
 
55
- define_method("has_" + target + "_target") do
56
- `return this[#{"has" + target.capitalize + "Target"}]`
78
+ define_method("has_" + ruby_name + "_target") do
79
+ `this[#{"has" + js_name.capitalize + "Target"}]`
57
80
  end
58
81
 
59
- snake_case_connected = target + "_target_connected"
60
- camel_case_connected = target + "TargetConnected"
82
+ snake_case_connected = ruby_name + "_target_connected"
83
+ camel_case_connected = js_name + "TargetConnected"
61
84
  %x{
62
85
  #{self.stimulus_controller}.prototype[#{camel_case_connected}] = function() {
63
86
  if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_connected})) {
@@ -66,8 +89,8 @@ class StimulusController < `Controller`
66
89
  }
67
90
  }
68
91
 
69
- snake_case_disconnected = target + "_target_disconnected"
70
- camel_case_disconnected = target + "TargetDisconnected"
92
+ snake_case_disconnected = ruby_name + "_target_disconnected"
93
+ camel_case_disconnected = js_name + "TargetDisconnected"
71
94
  %x{
72
95
  #{self.stimulus_controller}.prototype[#{camel_case_disconnected}] = function() {
73
96
  if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_disconnected})) {
@@ -82,22 +105,23 @@ class StimulusController < `Controller`
82
105
  `#{self.stimulus_controller}.outlets = outlets`
83
106
 
84
107
  outlets.each do |outlet|
85
- define_method(outlet + "_outlet") do
86
- JS::Proxy.new(`this[#{outlet + "Outlet"}]`)
108
+ js_name = outlet.to_s
109
+ ruby_name = self.to_ruby_name(outlet)
110
+
111
+ define_method(ruby_name + "_outlet") do
112
+ `return this[#{js_name + "Outlet"}]`
87
113
  end
88
114
 
89
- define_method(outlet + "_outlets") do
90
- `this[#{outlet + "Outlets"}]`.map do |outlet|
91
- JS::Proxy.new(outlet)
92
- end
115
+ define_method(ruby_name + "_outlets") do
116
+ `this[#{js_name + "Outlets"}]`
93
117
  end
94
118
 
95
- define_method("has_" + outlet + "_outlet") do
96
- `return this[#{"has" + outlet.capitalize + "Outlet"}]`
119
+ define_method("has_" + ruby_name + "_outlet") do
120
+ `return this[#{"has" + js_name.capitalize + "Outlet"}]`
97
121
  end
98
122
 
99
- snake_case_connected = outlet + "_outlet_connected"
100
- camel_case_connected = outlet + "OutletConnected"
123
+ snake_case_connected = ruby_name + "_outlet_connected"
124
+ camel_case_connected = js_name + "OutletConnected"
101
125
  %x{
102
126
  #{self.stimulus_controller}.prototype[#{camel_case_connected}] = function() {
103
127
  if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_connected})) {
@@ -106,8 +130,8 @@ class StimulusController < `Controller`
106
130
  }
107
131
  }
108
132
 
109
- snake_case_disconnected = outlet + "_outlet_disconnected"
110
- camel_case_disconnected = outlet + "OutletDisconnected"
133
+ snake_case_disconnected = ruby_name + "_outlet_disconnected"
134
+ camel_case_disconnected = js_name + "OutletDisconnected"
111
135
  %x{
112
136
  #{self.stimulus_controller}.prototype[#{camel_case_disconnected}] = function() {
113
137
  if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_disconnected})) {
@@ -122,37 +146,29 @@ class StimulusController < `Controller`
122
146
  js_values = {}
123
147
 
124
148
  values_hash.each do |name, type|
149
+ name = self.to_ruby_name(name)
150
+
125
151
  js_type = case type
126
- when String then "String"
127
- when Integer, Float, Numeric then "Number"
128
- when TrueClass, FalseClass, Boolean then "Boolean"
129
- when Array then "Array"
130
- when Hash, Object then "Object"
131
- else "String" # Default to String for unknown types
152
+ when :string then `String`
153
+ when :number then `Number`
154
+ when :boolean then `Boolean`
155
+ when :array then `Array`
156
+ when :object then `Object`
157
+ else
158
+ raise ArgumentError,
159
+ "Unsupported value type: #{type}, please use :string, :number, :boolean, :array, or :object"
132
160
  end
133
161
 
134
162
  js_values[name] = js_type
135
163
 
136
- # Define value getter method (snake_case)
137
- define_method(name.to_s) do
138
- # Convert JavaScript value to appropriate Ruby type
139
- js_value = `this[#{name + "Value"}]`
140
- case type
141
- when String
142
- js_value.to_s
143
- when Integer
144
- js_value.to_i
145
- when Float
146
- js_value.to_f
147
- when TrueClass, FalseClass, Boolean
148
- !!js_value
149
- when Array
150
- Native::Array.new(js_value)
151
- when Hash, Object
152
- Native::Object.new(js_value)
153
- else
154
- js_value
155
- end
164
+ `#{self.stimulus_controller}.values = #{js_values.to_n}`
165
+
166
+ define_method(name + "_value") do
167
+ Native(`this[#{name + "Value"}]`)
168
+ end
169
+
170
+ define_method(name + "_value=") do |value|
171
+ Native(`this[#{name + "Value"}]= #{value}`)
156
172
  end
157
173
 
158
174
  define_method("has_#{name}") do
@@ -163,75 +179,48 @@ class StimulusController < `Controller`
163
179
  camel_case_changed = "#{name}ValueChanged"
164
180
  %x{
165
181
  #{self.stimulus_controller}.prototype[#{camel_case_changed}] = function(value, previousValue) {
182
+ if (#{type == :object}) {
183
+ value = JSON.stringify(value)
184
+ previousValue = JSON.stringify(previousValue)
185
+ }
166
186
  if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_changed})) {
167
187
  return this['$' + #{snake_case_changed}](value, previousValue);
168
188
  }
169
189
  }
170
190
  }
171
191
  end
172
-
173
- `#{self.stimulus_controller}.values = #{js_values.to_n}`
174
192
  end
175
193
 
176
194
  def self.classes=(class_names = [])
177
- `#{self.stimulus_controller}.classes = #{class_names.to_n}`
195
+ `#{self.stimulus_controller}.classes = class_names`
178
196
 
179
197
  class_names.each do |class_name|
180
- define_method("add_#{class_name}_class") do
181
- `this.#{class_name}Classes.add()`
182
- end
198
+ js_name = class_name.to_s
199
+ ruby_name = self.to_ruby_name(class_name)
183
200
 
184
- define_method("remove_#{class_name}_class") do
185
- `this.#{class_name}Classes.remove()`
201
+ define_method("#{ruby_name}_class") do
202
+ `return this[#{js_name + "Class"}]`
186
203
  end
187
204
 
188
- define_method("has_#{class_name}_class?") do
189
- `return this.#{class_name}Classes.has()`
205
+ define_method("#{ruby_name}_classes") do
206
+ `return this[#{js_name + "Classes"}]`
190
207
  end
191
208
 
192
- define_method("toggle_#{class_name}_class") do
193
- `this.#{class_name}Classes.toggle()`
209
+ define_method("has_#{ruby_name}_class") do
210
+ `return this[#{"has" + js_name.capitalize + "Class"}]`
194
211
  end
195
212
  end
196
213
  end
197
214
 
198
- def add_class(class_name, element = nil)
199
- if element
200
- `this.addClass(#{class_name}, #{element})`
201
- else
202
- `this.addClass(#{class_name})`
203
- end
204
- end
205
-
206
- def remove_class(class_name, element = nil)
207
- if element
208
- `this.removeClass(#{class_name}, #{element})`
209
- else
210
- `this.removeClass(#{class_name})`
211
- end
212
- end
213
-
214
- def has_class?(class_name, element = nil)
215
- if element
216
- `return this.hasClass(#{class_name}, #{element})`
217
- else
218
- `return this.hasClass(#{class_name})`
219
- end
215
+ def element
216
+ JS::Proxy.new(`this.element`)
220
217
  end
221
218
 
222
- def toggle_class(class_name, force = nil, element = nil)
223
- if element && force != nil
224
- `this.toggleClass(#{class_name}, #{force}, #{element})`
225
- elsif element
226
- `this.toggleClass(#{class_name}, #{element})`
227
- elsif force != nil
228
- `this.toggleClass(#{class_name}, #{force})`
229
- else
230
- `this.toggleClass(#{class_name})`
231
- end
219
+ def window
220
+ @window ||= JS::Proxy.new($$.window)
232
221
  end
233
222
 
234
- def element
235
- `this.element`
223
+ def document
224
+ @document ||= JS::Proxy.new($$.document)
236
225
  end
237
226
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpalStimulus
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
5
5
  end
@@ -0,0 +1,53 @@
1
+ require "bundler/setup"
2
+ require "listen"
3
+ require "opal"
4
+
5
+ namespace :opal_stimulus do
6
+ def prepare_paths
7
+ [ "opal_proxy", "opal_stimulus" ].each do |gem_name|
8
+ Opal.use_gem(gem_name) rescue Opal.append_path(File.expand_path("lib", Bundler.rubygems.find_name(gem_name).first.full_gem_path))
9
+ end
10
+
11
+ Opal.append_path(Rails.root.join("app/opal"))
12
+ end
13
+
14
+ def compile
15
+ puts "🔨 Compiling Opal..."
16
+
17
+ builder = Opal::Builder.new
18
+ result = builder.build("application")
19
+ output_path = Rails.root.join("app/assets/builds/opal.js")
20
+ code = result.to_s
21
+
22
+ if Rails.env.development?
23
+ code += "//# sourceMappingURL=/assets/opal.js.map"
24
+ sourcemap_path = "#{output_path}.map"
25
+ source_map_json = result.source_map.to_json
26
+ File.write(sourcemap_path, source_map_json)
27
+ end
28
+
29
+ File.write(output_path, code)
30
+
31
+ puts "✅ Compiled to #{output_path}"
32
+ end
33
+
34
+ desc "Build Opal Stimulus controllers"
35
+ task build: [:environment] do
36
+ prepare_paths
37
+ compile
38
+ end
39
+
40
+ desc "Watch and build Opal Stimulus controllers"
41
+ task watch: [:environment] do
42
+ prepare_paths
43
+
44
+ compile
45
+
46
+ listen = Listen.to(Rails.root.join("app/opal")) { compile }
47
+
48
+ puts "👀 Watching app/opal for changes..."
49
+ listen.start
50
+ Signal.trap("INT") { listen.stop }
51
+ sleep
52
+ end
53
+ end
@@ -1,6 +1,6 @@
1
1
  namespace :opal_stimulus do
2
2
  desc "Install Opal Stimulus into the app"
3
- task :install do
3
+ task install: [:environment] do
4
4
  system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/install_opal_stimulus.rb", __dir__)}"
5
5
  end
6
6
  end