opal_stimulus 0.1.4 → 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 +4 -4
- data/.rspec-opal +2 -0
- data/CHANGELOG.md +13 -7
- data/README.md +12 -2
- data/Rakefile +12 -1
- data/lib/generators/USAGE +14 -0
- data/lib/generators/opal_stimulus_generator.rb +9 -0
- data/lib/generators/templates/controller.rb.tt +14 -0
- data/lib/install/Procfile.dev +1 -1
- data/lib/install/application.rb +2 -7
- data/lib/install/install_opal_stimulus.rb +2 -13
- data/lib/opal_stimulus/stimulus_controller.rb +22 -13
- data/lib/opal_stimulus/version.rb +1 -1
- data/lib/tasks/build.rake +53 -0
- data/lib/tasks/install.rake +1 -1
- data/shared_fixtures/stimulus@3.2.2.umd.js +2588 -0
- data/spec-opal/spec_helper.rb +24 -0
- data/spec-opal/stimulus_controller_spec.rb +215 -0
- metadata +11 -6
- data/lib/install/controllers/my_opal_controller.rb +0 -5
- data/lib/install/controllers_requires.rb +0 -4
- data/lib/install/opal +0 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f328bd466f89d10419369095e006b923c4a389c58fa14abd7c57ae2c57fb804d
|
4
|
+
data.tar.gz: 7b6a16a7adc502cdedcdad7e0e71f152652022a42d5a674fc033f6f13e514660
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 775c06738928c9c1584d0515064cf7e5984ff1259cb7def3c56cd9bc34942a7da50a39a761400d9f80a824a178d3333887fedfcfec4274adfa261a3819614d59
|
7
|
+
data.tar.gz: f3da082b6361dc0281e743a7ded76c953504c044abe2fc5b59febbd2d68e65138bb4eea4ef2acaab08f8a3a3073c655c3e325c0084f15bce58bd8d23f30d0b2e
|
data/.rspec-opal
ADDED
data/CHANGELOG.md
CHANGED
@@ -19,10 +19,16 @@
|
|
19
19
|
|
20
20
|
## [0.1.4] - 2025-08-03
|
21
21
|
|
22
|
-
Fix Targets
|
23
|
-
Fix Outlets
|
24
|
-
Fix Values
|
25
|
-
Fix `element`
|
26
|
-
Fix Classes
|
27
|
-
|
28
|
-
|
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
|
+
[](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
|
|
@@ -178,6 +185,9 @@ class DocumentController < StimulusController
|
|
178
185
|
end
|
179
186
|
```
|
180
187
|
|
188
|
+
## Run tests
|
189
|
+
`bundle exec rake`
|
190
|
+
|
181
191
|
## Contributing
|
182
192
|
|
183
193
|
Bug reports and pull requests are welcome on GitHub at https://github.com/josephschito/opal_stimulus.
|
data/Rakefile
CHANGED
@@ -9,4 +9,15 @@ require "rubocop/rake_task"
|
|
9
9
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
|
12
|
-
|
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 -%>
|
data/lib/install/Procfile.dev
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
web: bin/rails server
|
2
|
-
opal: bin/
|
2
|
+
opal: bin/rails opal_stimulus:watch
|
data/lib/install/application.rb
CHANGED
@@ -1,9 +1,4 @@
|
|
1
1
|
require "opal_stimulus/stimulus_controller"
|
2
|
-
|
2
|
+
require_tree "controllers"
|
3
3
|
|
4
|
-
StimulusController.
|
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/
|
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
|
-
|
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
|
|
@@ -44,6 +42,22 @@ class StimulusController < `Controller`
|
|
44
42
|
}
|
45
43
|
end
|
46
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
|
+
|
47
61
|
def self.targets=(targets = [])
|
48
62
|
`#{self.stimulus_controller}.targets = targets`
|
49
63
|
|
@@ -150,11 +164,11 @@ class StimulusController < `Controller`
|
|
150
164
|
`#{self.stimulus_controller}.values = #{js_values.to_n}`
|
151
165
|
|
152
166
|
define_method(name + "_value") do
|
153
|
-
`
|
167
|
+
Native(`this[#{name + "Value"}]`)
|
154
168
|
end
|
155
169
|
|
156
170
|
define_method(name + "_value=") do |value|
|
157
|
-
`this[#{name + "Value"}]= #{value
|
171
|
+
Native(`this[#{name + "Value"}]= #{value}`)
|
158
172
|
end
|
159
173
|
|
160
174
|
define_method("has_#{name}") do
|
@@ -165,6 +179,10 @@ class StimulusController < `Controller`
|
|
165
179
|
camel_case_changed = "#{name}ValueChanged"
|
166
180
|
%x{
|
167
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
|
+
}
|
168
186
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_changed})) {
|
169
187
|
return this['$' + #{snake_case_changed}](value, previousValue);
|
170
188
|
}
|
@@ -198,15 +216,6 @@ class StimulusController < `Controller`
|
|
198
216
|
JS::Proxy.new(`this.element`)
|
199
217
|
end
|
200
218
|
|
201
|
-
private
|
202
|
-
|
203
|
-
def self.to_ruby_name(name)
|
204
|
-
name
|
205
|
-
.to_s
|
206
|
-
.gsub(/([A-Z]+)/) { "_#{$1.downcase}" }
|
207
|
-
.sub(/^_/, '')
|
208
|
-
end
|
209
|
-
|
210
219
|
def window
|
211
220
|
@window ||= JS::Proxy.new($$.window)
|
212
221
|
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
|
data/lib/tasks/install.rake
CHANGED