opal-vite 0.3.14 → 0.3.16
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/lib/opal/vite/compiler.rb +24 -1
- data/lib/opal/vite/testing/stable_helpers.rb +16 -0
- data/lib/opal/vite/version.rb +1 -1
- data/lib/opal-vite.rb +6 -3
- data/opal/opal_vite/compat/opal_stimulus.rb +168 -0
- data/opal/opal_vite/concerns/v1/functional_component.rb +4 -1
- data/opal/opal_vite/concerns/v1/stimulus_helpers.rb +24 -4
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c20b89cec58a20be779bfbfd9bbffdf4c2dc337fa654e82791d346130a8f8f1
|
|
4
|
+
data.tar.gz: a51c0fe478c873b9aacd5df3080d06b3173cafa5b7b08225be113cb24471b90d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e270c06cdd81265b491f9f51b55493438138029d3cd106e482870542dd4479a355cae65042b017c18c2943311f1cd83b45137bd0332bcc685c182fe82556a6ad
|
|
7
|
+
data.tar.gz: 874b556449b4603cdf752af695f4cfa60b42d9b659d0d17b1e786748d14f345b5e967c343f7d3aa36913944391dd026320ad3f8ca9b7ce524731ddcabc80c333
|
data/lib/opal/vite/compiler.rb
CHANGED
|
@@ -12,6 +12,10 @@ module Opal
|
|
|
12
12
|
@config = options[:config] || Opal::Vite.config
|
|
13
13
|
@include_concerns = options.fetch(:include_concerns, true)
|
|
14
14
|
@stubs = options.fetch(:stubs, [])
|
|
15
|
+
# When true, Opal's corelib is treated as already loaded and left out
|
|
16
|
+
# of the output. The Vite plugin then imports the shared runtime
|
|
17
|
+
# (`/@opal-runtime`) instead, so corelib is only loaded once.
|
|
18
|
+
@external_runtime = options.fetch(:external_runtime, false)
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
# Compile Ruby source code to JavaScript
|
|
@@ -20,6 +24,7 @@ module Opal
|
|
|
20
24
|
begin
|
|
21
25
|
# Use Opal::Builder and add the file's directory to load paths
|
|
22
26
|
builder = Opal::Builder.new(stubs: @stubs)
|
|
27
|
+
builder.prerequired = self.class.runtime_requires if @external_runtime
|
|
23
28
|
|
|
24
29
|
# Add the directory containing the file to load paths
|
|
25
30
|
# This allows require statements to work relative to the file
|
|
@@ -68,10 +73,24 @@ module Opal
|
|
|
68
73
|
end
|
|
69
74
|
|
|
70
75
|
# Get the Opal runtime code
|
|
76
|
+
#
|
|
77
|
+
# The runtime's entry file (opal.rb) is not registered as a requirable
|
|
78
|
+
# module, so mark it as loaded explicitly. Otherwise `require 'opal'` in
|
|
79
|
+
# a bundle compiled with external_runtime raises LoadError at runtime.
|
|
71
80
|
def self.runtime_code
|
|
72
81
|
builder = Opal::Builder.new
|
|
73
82
|
builder.build('opal')
|
|
74
|
-
builder.to_s
|
|
83
|
+
"#{builder.to_s}\nOpal.loaded([\"opal\"]);\n"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Logical require paths that are part of runtime_code (opal itself and
|
|
87
|
+
# the corelib files it pulls in).
|
|
88
|
+
def self.runtime_requires
|
|
89
|
+
@runtime_requires ||= begin
|
|
90
|
+
builder = Opal::Builder.new
|
|
91
|
+
builder.build('opal')
|
|
92
|
+
['opal', *builder.already_processed.to_a].freeze
|
|
93
|
+
end
|
|
75
94
|
end
|
|
76
95
|
|
|
77
96
|
private
|
|
@@ -107,6 +126,10 @@ module Opal
|
|
|
107
126
|
end
|
|
108
127
|
end
|
|
109
128
|
|
|
129
|
+
# opal-vite's own opal/ directory holds the built-in concerns; it is
|
|
130
|
+
# on $LOAD_PATH like any gem, so drop it here when they are disabled.
|
|
131
|
+
gem_opal_paths.delete(File.expand_path(Opal::Vite.opal_lib_path)) unless @include_concerns
|
|
132
|
+
|
|
110
133
|
# Add opal directories FIRST so they take priority over lib directories
|
|
111
134
|
# This ensures that 'require "inesita"' finds opal/inesita.rb before lib/inesita.rb
|
|
112
135
|
gem_opal_paths.uniq.each do |path|
|
|
@@ -224,6 +224,22 @@ module Opal
|
|
|
224
224
|
js_poll_until(js_condition, timeout: timeout, error_message: "Condition not met: #{js_condition}")
|
|
225
225
|
end
|
|
226
226
|
|
|
227
|
+
# Wait until every Stimulus controller declared on the page is connected
|
|
228
|
+
# (expects the Application on window.Stimulus). Checking for elements or
|
|
229
|
+
# targets is not enough: they are in the static HTML before Opal has
|
|
230
|
+
# registered the controllers, so tests could act before connect() ran.
|
|
231
|
+
# @param timeout [Integer] Maximum wait time in seconds
|
|
232
|
+
def wait_for_stimulus_connected(timeout: DEFAULT_TIMEOUT)
|
|
233
|
+
js_poll_until(<<~JS.strip, timeout: timeout, error_message: "Stimulus controllers not connected within #{timeout}s")
|
|
234
|
+
(typeof Stimulus !== 'undefined') &&
|
|
235
|
+
Array.from(document.querySelectorAll('[data-controller]')).every(function(el) {
|
|
236
|
+
return el.getAttribute('data-controller').split(/\\s+/).filter(Boolean).every(function(id) {
|
|
237
|
+
return !!Stimulus.getControllerForElementAndIdentifier(el, id);
|
|
238
|
+
});
|
|
239
|
+
})
|
|
240
|
+
JS
|
|
241
|
+
end
|
|
242
|
+
|
|
227
243
|
# Wait for DOM to be stable (no pending mutations)
|
|
228
244
|
# Uses MutationObserver setup in JS with Ruby-based polling
|
|
229
245
|
# @param timeout [Integer] Maximum wait time in seconds
|
data/lib/opal/vite/version.rb
CHANGED
data/lib/opal-vite.rb
CHANGED
|
@@ -24,7 +24,8 @@ module Opal
|
|
|
24
24
|
# Returns the path to the opal/ directory in this gem
|
|
25
25
|
# Contains built-in concerns like StimulusHelpers
|
|
26
26
|
def opal_lib_path
|
|
27
|
-
|
|
27
|
+
# This file is lib/opal-vite.rb, so opal/ is one level up
|
|
28
|
+
File.expand_path('../opal', __dir__)
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
# CLI entry point for compilation
|
|
@@ -32,12 +33,14 @@ module Opal
|
|
|
32
33
|
# @param include_concerns [Boolean] Whether to include built-in concerns
|
|
33
34
|
# @param source_map [Boolean] Whether to generate source maps
|
|
34
35
|
# @param stubs [Array<String>] List of modules to stub (return empty implementations)
|
|
35
|
-
|
|
36
|
+
# @param external_runtime [Boolean] Leave Opal's corelib out of the output
|
|
37
|
+
# (the caller loads it separately, e.g. via the `/@opal-runtime` module)
|
|
38
|
+
def compile_for_vite(file_path, include_concerns: true, source_map: true, stubs: [], external_runtime: false)
|
|
36
39
|
# Temporarily override source map setting if specified
|
|
37
40
|
original_source_map = config.source_map_enabled
|
|
38
41
|
config.source_map_enabled = source_map
|
|
39
42
|
|
|
40
|
-
compiler = Compiler.new(include_concerns: include_concerns, stubs: stubs)
|
|
43
|
+
compiler = Compiler.new(include_concerns: include_concerns, stubs: stubs, external_runtime: external_runtime)
|
|
41
44
|
result = compiler.compile_file(file_path)
|
|
42
45
|
|
|
43
46
|
# Output JSON to stdout for the Vite plugin to consume
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# backtick_javascript: true
|
|
2
|
+
#
|
|
3
|
+
# Fixes for opal_stimulus (0.2.x) accessors that look up the wrong
|
|
4
|
+
# JavaScript property for multi-word names.
|
|
5
|
+
#
|
|
6
|
+
# opal_stimulus builds names such as "hasSoundButtonTarget" with
|
|
7
|
+
# String#capitalize, which lowercases the rest of the word ("hasSoundbuttonTarget"),
|
|
8
|
+
# and passes snake_case value names straight to Stimulus. As a result:
|
|
9
|
+
#
|
|
10
|
+
# - has_*_target / has_*_outlet / has_*_class always return undefined for
|
|
11
|
+
# multi-word names
|
|
12
|
+
# - multi-word values read and write the wrong property, use the wrong
|
|
13
|
+
# data attribute, and never fire *_value_changed
|
|
14
|
+
# - dashed outlet names (e.g. "user-status") read the wrong property and never
|
|
15
|
+
# fire *_outlet_connected / *_outlet_disconnected
|
|
16
|
+
#
|
|
17
|
+
# Usage (after opal_stimulus, before defining controllers):
|
|
18
|
+
#
|
|
19
|
+
# require 'opal_stimulus/stimulus_controller'
|
|
20
|
+
# require 'opal_vite/compat/opal_stimulus'
|
|
21
|
+
#
|
|
22
|
+
# Values declared as `self.values = { latest_paid: :number }` then map to
|
|
23
|
+
# Stimulus' latestPaidValue / data-<controller>-latest-paid-value, as they
|
|
24
|
+
# would in a JavaScript controller.
|
|
25
|
+
require 'opal_stimulus/stimulus_controller'
|
|
26
|
+
|
|
27
|
+
module OpalVite
|
|
28
|
+
module Compat
|
|
29
|
+
module OpalStimulus
|
|
30
|
+
# "soundButton" -> "SoundButton" (String#capitalize gives "Soundbutton")
|
|
31
|
+
def self.upcase_first(name)
|
|
32
|
+
str = name.to_s
|
|
33
|
+
str.empty? ? str : str[0].upcase + str[1..-1]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Stimulus' camelize: "latest_paid" / "user-status" -> "latestPaid" / "userStatus"
|
|
37
|
+
def self.camelize(name)
|
|
38
|
+
name.to_s.gsub(/[_-]([a-z0-9])/) { $1.upcase }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Stimulus' namespaceCamelize for outlet names ("admin--user-status" -> "adminUserStatus")
|
|
42
|
+
def self.namespace_camelize(name)
|
|
43
|
+
camelize(name.to_s.gsub('--', '-').gsub('__', '_'))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module ClassMethods
|
|
47
|
+
def targets=(targets = [])
|
|
48
|
+
super
|
|
49
|
+
|
|
50
|
+
targets.each do |target|
|
|
51
|
+
has_prop = "has#{OpalStimulus.upcase_first(target)}Target"
|
|
52
|
+
define_method("has_#{to_ruby_name(target)}_target") do
|
|
53
|
+
`this[#{has_prop}]`
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def outlets=(outlets = [])
|
|
59
|
+
super
|
|
60
|
+
|
|
61
|
+
outlets.each do |outlet|
|
|
62
|
+
ruby_name = to_ruby_name(outlet).tr('-', '_')
|
|
63
|
+
js_name = OpalStimulus.namespace_camelize(outlet)
|
|
64
|
+
outlet_prop = "#{js_name}Outlet"
|
|
65
|
+
outlets_prop = "#{js_name}Outlets"
|
|
66
|
+
has_prop = "has#{OpalStimulus.upcase_first(js_name)}Outlet"
|
|
67
|
+
|
|
68
|
+
define_method("#{ruby_name}_outlet") do
|
|
69
|
+
`this[#{outlet_prop}]`
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
define_method("#{ruby_name}_outlets") do
|
|
73
|
+
`this[#{outlets_prop}]`
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
define_method("has_#{ruby_name}_outlet") do
|
|
77
|
+
`this[#{has_prop}]`
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Stimulus calls the camelized callbacks; forward them to Ruby.
|
|
81
|
+
["connected", "disconnected"].each do |event|
|
|
82
|
+
snake_case_callback = "#{ruby_name}_outlet_#{event}"
|
|
83
|
+
camel_case_callback = "#{js_name}Outlet#{OpalStimulus.upcase_first(event)}"
|
|
84
|
+
%x{
|
|
85
|
+
#{stimulus_controller}.prototype[#{camel_case_callback}] = function(outlet, element) {
|
|
86
|
+
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_callback})) {
|
|
87
|
+
return this['$' + #{snake_case_callback}](outlet, element);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Stimulus uses the class key as-is for xxxClass / xxxClasses, so only
|
|
96
|
+
# the has_ accessor needs fixing.
|
|
97
|
+
def classes=(class_names = [])
|
|
98
|
+
super
|
|
99
|
+
|
|
100
|
+
class_names.each do |class_name|
|
|
101
|
+
has_prop = "has#{OpalStimulus.upcase_first(class_name)}Class"
|
|
102
|
+
define_method("has_#{to_ruby_name(class_name)}_class") do
|
|
103
|
+
`this[#{has_prop}]`
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Replaces (does not wrap) the original: the original registers
|
|
109
|
+
# snake_case keys with Stimulus, which changes the data attribute
|
|
110
|
+
# name, so it cannot be fixed after the fact.
|
|
111
|
+
def values=(values_hash = {})
|
|
112
|
+
js_values = {}
|
|
113
|
+
|
|
114
|
+
values_hash.each do |name, type|
|
|
115
|
+
ruby_name = to_ruby_name(name)
|
|
116
|
+
js_name = OpalStimulus.camelize(ruby_name)
|
|
117
|
+
|
|
118
|
+
js_type = case type
|
|
119
|
+
when :string then `String`
|
|
120
|
+
when :number then `Number`
|
|
121
|
+
when :boolean then `Boolean`
|
|
122
|
+
when :array then `Array`
|
|
123
|
+
when :object then `Object`
|
|
124
|
+
else
|
|
125
|
+
raise ArgumentError,
|
|
126
|
+
"Unsupported value type: #{type}, please use :string, :number, :boolean, :array, or :object"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
js_values[js_name] = js_type
|
|
130
|
+
|
|
131
|
+
value_prop = "#{js_name}Value"
|
|
132
|
+
has_prop = "has#{OpalStimulus.upcase_first(js_name)}Value"
|
|
133
|
+
|
|
134
|
+
define_method("#{ruby_name}_value") do
|
|
135
|
+
Native(`this[#{value_prop}]`)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
define_method("#{ruby_name}_value=") do |value|
|
|
139
|
+
Native(`this[#{value_prop}] = #{value}`)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
define_method("has_#{ruby_name}") do
|
|
143
|
+
`this[#{has_prop}]`
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
snake_case_changed = "#{ruby_name}_value_changed"
|
|
147
|
+
camel_case_changed = "#{js_name}ValueChanged"
|
|
148
|
+
%x{
|
|
149
|
+
#{stimulus_controller}.prototype[#{camel_case_changed}] = function(value, previousValue) {
|
|
150
|
+
if (#{type == :object}) {
|
|
151
|
+
value = JSON.stringify(value)
|
|
152
|
+
previousValue = JSON.stringify(previousValue)
|
|
153
|
+
}
|
|
154
|
+
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_changed})) {
|
|
155
|
+
return this['$' + #{snake_case_changed}](value, previousValue);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
`#{stimulus_controller}.values = #{js_values.to_n}`
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
StimulusController.singleton_class.prepend(OpalVite::Compat::OpalStimulus::ClassMethods)
|
|
@@ -217,7 +217,10 @@ module OpalVite
|
|
|
217
217
|
# button({ onClick: set_count.with { |c| c + 1 } }, '+')
|
|
218
218
|
def with(&block)
|
|
219
219
|
setter_fn = @setter_fn
|
|
220
|
-
|
|
220
|
+
# Opal emits a multi-line x-string as a statement (no implicit
|
|
221
|
+
# return), and a bare anonymous function statement is a
|
|
222
|
+
# JavaScript syntax error, so return explicitly.
|
|
223
|
+
`return function() {
|
|
221
224
|
#{setter_fn}(function(current) {
|
|
222
225
|
return #{block.call(`current`)};
|
|
223
226
|
});
|
|
@@ -857,11 +857,12 @@ module OpalVite
|
|
|
857
857
|
end
|
|
858
858
|
|
|
859
859
|
# Call a method on a JavaScript object
|
|
860
|
-
# @param obj [
|
|
860
|
+
# @param obj [Object] JavaScript object (raw, Native or JS::Proxy)
|
|
861
861
|
# @param method [Symbol, String] Method name
|
|
862
862
|
# @param args [Array] Arguments to pass
|
|
863
863
|
# @return [Native] Method return value
|
|
864
864
|
def js_call_on(obj, method, *args)
|
|
865
|
+
obj = to_native_element(obj)
|
|
865
866
|
if args.empty?
|
|
866
867
|
`#{obj}[#{method.to_s}]()`
|
|
867
868
|
else
|
|
@@ -871,21 +872,40 @@ module OpalVite
|
|
|
871
872
|
end
|
|
872
873
|
|
|
873
874
|
# Get a property from a JavaScript object
|
|
874
|
-
#
|
|
875
|
+
#
|
|
876
|
+
# Unlike calling the property as a method on a JS::Proxy (e.g. the
|
|
877
|
+
# elements opal_stimulus' `*_target` methods return), a missing property
|
|
878
|
+
# does not raise NoMethodError; you get JavaScript's undefined.
|
|
879
|
+
#
|
|
880
|
+
# @param obj [Object] JavaScript object (raw, Native or JS::Proxy)
|
|
875
881
|
# @param prop [Symbol, String] Property name
|
|
876
|
-
# @return [
|
|
882
|
+
# @return [Object] Raw property value (may be undefined)
|
|
877
883
|
def js_get(obj, prop)
|
|
884
|
+
obj = to_native_element(obj)
|
|
878
885
|
`#{obj}[#{prop.to_s}]`
|
|
879
886
|
end
|
|
880
887
|
|
|
881
888
|
# Set a property on a JavaScript object
|
|
882
|
-
# @param obj [
|
|
889
|
+
# @param obj [Object] JavaScript object (raw, Native or JS::Proxy)
|
|
883
890
|
# @param prop [Symbol, String] Property name
|
|
884
891
|
# @param value [Object] Value to set
|
|
885
892
|
def js_set(obj, prop, value)
|
|
893
|
+
obj = to_native_element(obj)
|
|
886
894
|
`#{obj}[#{prop.to_s}] = #{value}`
|
|
887
895
|
end
|
|
888
896
|
|
|
897
|
+
# Read a data-* attribute through element.dataset
|
|
898
|
+
# @param element [Object] DOM element (raw, Native or JS::Proxy)
|
|
899
|
+
# @param key [Symbol, String] Attribute name without "data-", in
|
|
900
|
+
# snake_case or camelCase (:confirm_message and :confirmMessage both
|
|
901
|
+
# read data-confirm-message)
|
|
902
|
+
# @return [String, nil] The attribute value, or nil when it is absent
|
|
903
|
+
def dataset_value(element, key)
|
|
904
|
+
el = to_native_element(element)
|
|
905
|
+
name = camelize(key, false)
|
|
906
|
+
`(#{el}.dataset[#{name}] == null) ? nil : #{el}.dataset[#{name}]`
|
|
907
|
+
end
|
|
908
|
+
|
|
889
909
|
# ===== JSON Methods =====
|
|
890
910
|
|
|
891
911
|
# Parse JSON string to JavaScript object
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: opal-vite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.16
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- stofu1234
|
|
@@ -112,6 +112,7 @@ files:
|
|
|
112
112
|
- lib/opal/vite/testing/stable_helpers.rb
|
|
113
113
|
- lib/opal/vite/version.rb
|
|
114
114
|
- opal/opal_vite.rb
|
|
115
|
+
- opal/opal_vite/compat/opal_stimulus.rb
|
|
115
116
|
- opal/opal_vite/concerns.rb
|
|
116
117
|
- opal/opal_vite/concerns/dom_helpers.rb
|
|
117
118
|
- opal/opal_vite/concerns/js_proxy_ex.rb
|