opal-vite 0.2.5 → 0.2.7

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: 589d2115317b97d0c2c1452001b128f4ac3269d8e05c390e60e87d0446c8ebf0
4
- data.tar.gz: 2811c208a9e64dfd800d2f1d4540a052f348b82b03f255ff6cae820895af8098
3
+ metadata.gz: d647a605d1362c318322a04e13412cb9bb298fff93d9d2892b96afdc7170fc6b
4
+ data.tar.gz: 0a72ac91f6973d02587de1b63ec63052fce19519138ece126e7f9edd40af8c56
5
5
  SHA512:
6
- metadata.gz: a85ead98a10e206d858a508ef808f9410af6bf6597c6f167b93560da2cb706cbffb252b90ac5144eb9003ef45291bcdbdd84f8dab7c56425d08933be952bbbe8
7
- data.tar.gz: 117923f00c860592866c31fba50371b84a8c47007c822ce240fa377c510119b1f640443cfa338b78298e5fe59ec1c7d44137340c71503634a71f9b26d9813783
6
+ metadata.gz: 508a5e5f69a85c218c2ccf2f451140080860885a00d041909a5da09c9a7d755ba369ba6ecc10f2c4ed84c31a4491103495ec0afa022f9abfb6d2d8d349c16f7b
7
+ data.tar.gz: 4b84a15c0db47b794d0bb50a1eba4e6b78df5683f0fbf281d5e05f7f14ab2fb016ad59591b37b846b62125cc7035541e31772140a8c41e91285c628531e810ed
@@ -146,7 +146,7 @@ module Opal
146
146
  return nil unless builder.respond_to?(:source_map) && builder.source_map
147
147
 
148
148
  source_map = builder.source_map
149
- map_hash = source_map.to_h
149
+ map_hash = deep_stringify_keys(source_map.to_h)
150
150
  return nil unless map_hash
151
151
 
152
152
  # If it's an index format with sections, merge all sections
@@ -156,16 +156,35 @@ module Opal
156
156
 
157
157
  return nil unless map_hash
158
158
 
159
+ # Add sourceRoot for proper browser debugging
160
+ # This helps DevTools organize source files in a logical tree
161
+ map_hash['sourceRoot'] = ''
162
+
159
163
  # Normalize source paths for browser debugging
164
+ # Prefix with /opal-sources/ so they appear in a dedicated folder in DevTools
160
165
  if map_hash['sources']
161
166
  map_hash['sources'] = map_hash['sources'].map do |source|
162
- normalize_source_path(source, file_path)
167
+ normalize_source_path_for_devtools(source, file_path)
163
168
  end
164
169
  end
165
170
 
166
171
  map_hash.to_json
167
172
  end
168
173
 
174
+ # Recursively convert all hash keys to strings
175
+ def deep_stringify_keys(obj)
176
+ case obj
177
+ when Hash
178
+ obj.each_with_object({}) do |(key, value), result|
179
+ result[key.to_s] = deep_stringify_keys(value)
180
+ end
181
+ when Array
182
+ obj.map { |item| deep_stringify_keys(item) }
183
+ else
184
+ obj
185
+ end
186
+ end
187
+
169
188
  def merge_all_sections(index_map, file_path)
170
189
  sections = index_map['sections']
171
190
  return nil if sections.nil? || sections.empty?
@@ -186,6 +205,13 @@ module Opal
186
205
  'mappings' => ''
187
206
  }
188
207
 
208
+ # Track cumulative state for VLQ relative encoding
209
+ # These track the "previous" values across all sections
210
+ prev_source = 0
211
+ prev_orig_line = 0
212
+ prev_orig_col = 0
213
+ prev_name = 0
214
+
189
215
  current_line = 0
190
216
 
191
217
  sections.each_with_index do |section, idx|
@@ -202,7 +228,7 @@ module Opal
202
228
  current_line = section_start_line
203
229
  end
204
230
 
205
- # Track source index offset for this section
231
+ # Track source and name index offsets for this section
206
232
  source_offset = merged['sources'].length
207
233
  name_offset = merged['names'].length
208
234
 
@@ -223,25 +249,184 @@ module Opal
223
249
  merged['names'].concat(section_map['names'])
224
250
  end
225
251
 
226
- # Add mappings from this section
252
+ # Process mappings from this section with index adjustment
227
253
  if section_map['mappings'] && !section_map['mappings'].empty?
228
- # If we need to adjust source/name indices, we'd need to decode/re-encode VLQ
229
- # For now, append as-is (works when each section has its own source indices starting at 0)
230
- # This is a simplification - proper implementation would adjust indices
254
+ adjusted_mappings, prev_source, prev_orig_line, prev_orig_col, prev_name =
255
+ adjust_section_mappings(
256
+ section_map['mappings'],
257
+ source_offset,
258
+ name_offset,
259
+ prev_source,
260
+ prev_orig_line,
261
+ prev_orig_col,
262
+ prev_name
263
+ )
264
+
231
265
  if idx > 0 && !merged['mappings'].empty? && !merged['mappings'].end_with?(';')
232
266
  merged['mappings'] += ';'
233
267
  end
234
- merged['mappings'] += section_map['mappings']
268
+ merged['mappings'] += adjusted_mappings
235
269
 
236
- # Count lines in this section's mappings
237
- lines_in_section = section_map['mappings'].count(';') + 1
238
- current_line += lines_in_section
270
+ # Update current_line to the last line we wrote to
271
+ # section_start_line + number of semicolons in this section's mappings
272
+ current_line = section_start_line + section_map['mappings'].count(';')
239
273
  end
240
274
  end
241
275
 
242
276
  merged
243
277
  end
244
278
 
279
+ # Adjust mappings from a section by adding offsets to source/name indices
280
+ # VLQ mappings use relative deltas. When merging sections:
281
+ # - First section: no adjustment, just track final absolute state
282
+ # - Later sections: adjust first segment's source/name delta to bridge from previous section's end state
283
+ #
284
+ # Returns: [adjusted_mappings, new_prev_source, new_prev_orig_line, new_prev_orig_col, new_prev_name]
285
+ def adjust_section_mappings(mappings, source_offset, name_offset, prev_source, prev_orig_line, prev_orig_col, prev_name)
286
+ return ['', prev_source, prev_orig_line, prev_orig_col, prev_name] if mappings.nil? || mappings.empty?
287
+
288
+ result_lines = []
289
+ lines = mappings.split(';', -1)
290
+
291
+ # Track absolute state within this section (section-local, starting from 0)
292
+ section_abs_source = 0
293
+ section_abs_orig_line = 0
294
+ section_abs_orig_col = 0
295
+ section_abs_name = 0
296
+
297
+ first_segment_processed = false
298
+
299
+ lines.each do |line|
300
+ if line.empty?
301
+ result_lines << ''
302
+ next
303
+ end
304
+
305
+ result_segments = []
306
+ segments = line.split(',')
307
+
308
+ segments.each do |segment|
309
+ values = decode_vlq(segment)
310
+ next if values.empty?
311
+
312
+ # values[0] = generated column delta (always present, relative within line)
313
+ # values[1] = source index delta
314
+ # values[2] = original line delta
315
+ # values[3] = original column delta
316
+ # values[4] = name index delta
317
+
318
+ gen_col_delta = values[0]
319
+
320
+ if values.length > 1
321
+ # Update section-local absolute positions
322
+ section_abs_source += values[1]
323
+ section_abs_orig_line += values[2] if values.length > 2
324
+ section_abs_orig_col += values[3] if values.length > 3
325
+ section_abs_name += values[4] if values.length > 4
326
+
327
+ # Calculate global absolute positions (with offset)
328
+ global_abs_source = section_abs_source + source_offset
329
+ global_abs_name = section_abs_name + name_offset
330
+
331
+ if !first_segment_processed
332
+ # First segment: calculate delta from previous section's end state to this segment's global position
333
+ new_source_delta = global_abs_source - prev_source
334
+ new_orig_line_delta = section_abs_orig_line - prev_orig_line
335
+ new_orig_col_delta = section_abs_orig_col - prev_orig_col
336
+ new_name_delta = global_abs_name - prev_name
337
+
338
+ new_values = [gen_col_delta, new_source_delta, new_orig_line_delta, new_orig_col_delta]
339
+ new_values << new_name_delta if values.length > 4
340
+
341
+ first_segment_processed = true
342
+ else
343
+ # Subsequent segments: deltas are already correct (relative within section = relative within merged)
344
+ new_values = values.dup
345
+ end
346
+
347
+ result_segments << encode_vlq(new_values)
348
+
349
+ # Update tracking for next section
350
+ prev_source = global_abs_source
351
+ prev_orig_line = section_abs_orig_line
352
+ prev_orig_col = section_abs_orig_col
353
+ prev_name = global_abs_name if values.length > 4
354
+ else
355
+ # Only generated column, no source mapping
356
+ result_segments << encode_vlq([gen_col_delta])
357
+ end
358
+ end
359
+
360
+ result_lines << result_segments.join(',')
361
+ end
362
+
363
+ [result_lines.join(';'), prev_source, prev_orig_line, prev_orig_col, prev_name]
364
+ end
365
+
366
+ # VLQ Base64 character set
367
+ VLQ_BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.freeze
368
+ VLQ_BASE64_VALUES = VLQ_BASE64_CHARS.each_char.with_index.to_h.freeze
369
+ VLQ_BASE_SHIFT = 5
370
+ VLQ_BASE = 1 << VLQ_BASE_SHIFT # 32
371
+ VLQ_BASE_MASK = VLQ_BASE - 1 # 31
372
+ VLQ_CONTINUATION_BIT = VLQ_BASE # 32
373
+
374
+ # Decode a VLQ-encoded segment into an array of integers
375
+ def decode_vlq(segment)
376
+ return [] if segment.nil? || segment.empty?
377
+
378
+ values = []
379
+ shift = 0
380
+ value = 0
381
+
382
+ segment.each_char do |char|
383
+ digit = VLQ_BASE64_VALUES[char]
384
+ return values if digit.nil? # Invalid character
385
+
386
+ continuation = (digit & VLQ_CONTINUATION_BIT) != 0
387
+ digit &= VLQ_BASE_MASK
388
+ value += digit << shift
389
+
390
+ if continuation
391
+ shift += VLQ_BASE_SHIFT
392
+ else
393
+ # Convert from VLQ signed representation
394
+ negative = (value & 1) == 1
395
+ value >>= 1
396
+ value = -value if negative
397
+ values << value
398
+
399
+ # Reset for next value
400
+ value = 0
401
+ shift = 0
402
+ end
403
+ end
404
+
405
+ values
406
+ end
407
+
408
+ # Encode an array of integers into a VLQ-encoded string
409
+ def encode_vlq(values)
410
+ return '' if values.nil? || values.empty?
411
+
412
+ result = ''
413
+
414
+ values.each do |value|
415
+ # Convert to VLQ signed representation
416
+ vlq = value < 0 ? ((-value) << 1) + 1 : value << 1
417
+
418
+ loop do
419
+ digit = vlq & VLQ_BASE_MASK
420
+ vlq >>= VLQ_BASE_SHIFT
421
+ digit |= VLQ_CONTINUATION_BIT if vlq > 0
422
+ result += VLQ_BASE64_CHARS[digit]
423
+ break if vlq == 0
424
+ end
425
+ end
426
+
427
+ result
428
+ end
429
+
245
430
  def normalize_source_path(source, file_path)
246
431
  # Convert absolute paths to relative for better browser debugging
247
432
  return source if source.nil? || source.empty?
@@ -257,6 +442,39 @@ module Opal
257
442
  source
258
443
  end
259
444
  end
445
+
446
+ def normalize_source_path_for_devtools(source, file_path)
447
+ # Format source paths so they appear properly in browser DevTools
448
+ # Chrome DevTools uses the source path to build the file tree
449
+ return source if source.nil? || source.empty?
450
+
451
+ # Remove any leading ./ for consistency
452
+ source = source.sub(/^\.\//, '')
453
+
454
+ # Handle absolute paths - make them relative to show properly
455
+ if source.start_with?('/')
456
+ # Extract just the relevant path (last few components)
457
+ parts = source.split('/')
458
+ # Keep last 3 path components for context (e.g., app/opal/controllers/...)
459
+ source = parts.last(3).join('/')
460
+ end
461
+
462
+ # Prefix paths for user code (controllers, services, etc.) to group them
463
+ # This ensures they appear under a dedicated folder in DevTools
464
+ if source.include?('controllers/') || source.include?('services/')
465
+ # Already has recognizable path, prefix with opal-sources for visibility
466
+ "/opal-sources/#{source}"
467
+ elsif source.start_with?('corelib/') || source.start_with?('opal/')
468
+ # Opal core library files - put under opal-core
469
+ "/opal-core/#{source}"
470
+ elsif source.include?('opal_stimulus/') || source.include?('opal_vite/')
471
+ # Opal library files
472
+ "/opal-libs/#{source}"
473
+ else
474
+ # Other files (native.rb, js/proxy.rb, etc.)
475
+ "/opal-other/#{source}"
476
+ end
477
+ end
260
478
  end
261
479
  end
262
480
  end
@@ -1,5 +1,5 @@
1
1
  module Opal
2
2
  module Vite
3
- VERSION = "0.2.5"
3
+ VERSION = "0.2.7"
4
4
  end
5
5
  end
@@ -0,0 +1,249 @@
1
+ # backtick_javascript: true
2
+
3
+ # VueHelpers - DSL helpers for Vue.js 3 applications with Opal
4
+ # Reduces backtick JavaScript usage in Vue components
5
+ module VueHelpers
6
+ extend self # Makes all methods available as module methods
7
+ # ===================
8
+ # Vue Access
9
+ # ===================
10
+
11
+ # Get Vue from window
12
+ def vue
13
+ `window.Vue`
14
+ end
15
+
16
+ # Create a Vue application
17
+ # @param options [Hash] Vue component options (data, methods, computed, template, etc.)
18
+ # @return [Native] Vue app instance
19
+ def create_app(options = {})
20
+ Native(`window.Vue.createApp(#{options.to_n})`)
21
+ end
22
+
23
+ # Create a reactive ref
24
+ # @param initial_value [Object] Initial value
25
+ # @return [Native] Vue ref
26
+ def vue_ref(initial_value)
27
+ Native(`window.Vue.ref(#{initial_value})`)
28
+ end
29
+
30
+ # Create a reactive object
31
+ # @param object [Hash] Object to make reactive
32
+ # @return [Native] Vue reactive object
33
+ def vue_reactive(object)
34
+ Native(`window.Vue.reactive(#{object.to_n})`)
35
+ end
36
+
37
+ # Create a computed property
38
+ # @param getter [Proc] Getter function
39
+ # @return [Native] Vue computed ref
40
+ def vue_computed(&getter)
41
+ Native(`window.Vue.computed(#{getter})`)
42
+ end
43
+
44
+ # Watch a reactive source
45
+ # @param source [Native] Reactive source to watch
46
+ # @param callback [Proc] Callback function
47
+ def vue_watch(source, &callback)
48
+ `window.Vue.watch(#{source}, #{callback})`
49
+ end
50
+
51
+ # ===================
52
+ # Component Definition Helpers
53
+ # ===================
54
+
55
+ # Define component data as a function
56
+ # @param data_hash [Hash] Initial data
57
+ # @return [Proc] Data function for Vue component
58
+ def data_fn(data_hash)
59
+ -> { data_hash.to_n }
60
+ end
61
+
62
+ # Define methods hash for Vue component
63
+ # @param methods_hash [Hash] Methods with name => proc
64
+ # @return [Hash] Methods object for Vue component
65
+ def methods_obj(methods_hash)
66
+ result = {}
67
+ methods_hash.each do |name, proc|
68
+ result[name] = proc
69
+ end
70
+ result.to_n
71
+ end
72
+
73
+ # Define computed properties hash
74
+ # @param computed_hash [Hash] Computed properties with name => proc
75
+ # @return [Hash] Computed object for Vue component
76
+ def computed_obj(computed_hash)
77
+ result = {}
78
+ computed_hash.each do |name, proc|
79
+ result[name] = proc
80
+ end
81
+ result.to_n
82
+ end
83
+
84
+ # ===================
85
+ # Lifecycle Hooks
86
+ # ===================
87
+
88
+ # onMounted hook
89
+ def on_mounted(&block)
90
+ `window.Vue.onMounted(#{block})`
91
+ end
92
+
93
+ # onUnmounted hook
94
+ def on_unmounted(&block)
95
+ `window.Vue.onUnmounted(#{block})`
96
+ end
97
+
98
+ # onUpdated hook
99
+ def on_updated(&block)
100
+ `window.Vue.onUpdated(#{block})`
101
+ end
102
+
103
+ # onBeforeMount hook
104
+ def on_before_mount(&block)
105
+ `window.Vue.onBeforeMount(#{block})`
106
+ end
107
+
108
+ # onBeforeUnmount hook
109
+ def on_before_unmount(&block)
110
+ `window.Vue.onBeforeUnmount(#{block})`
111
+ end
112
+
113
+ # ===================
114
+ # Window/Global Access
115
+ # ===================
116
+
117
+ # Get a property from window
118
+ def window_get(key)
119
+ `window[#{key}]`
120
+ end
121
+
122
+ # Set a property on window
123
+ def window_set(key, value)
124
+ `window[#{key}] = #{value}`
125
+ end
126
+
127
+ # ===================
128
+ # Console
129
+ # ===================
130
+
131
+ # Console log
132
+ def console_log(*args)
133
+ `console.log(...#{args})`
134
+ end
135
+
136
+ # Console warn
137
+ def console_warn(*args)
138
+ `console.warn(...#{args})`
139
+ end
140
+
141
+ # Console error
142
+ def console_error(*args)
143
+ `console.error(...#{args})`
144
+ end
145
+
146
+ # ===================
147
+ # DOM Events
148
+ # ===================
149
+
150
+ # Execute block when DOM is ready
151
+ def on_dom_ready(&block)
152
+ `document.addEventListener('DOMContentLoaded', #{block})`
153
+ end
154
+
155
+ # ===================
156
+ # DOM Query
157
+ # ===================
158
+
159
+ # Query single element
160
+ def query(selector)
161
+ `document.querySelector(#{selector})`
162
+ end
163
+
164
+ # Query all elements
165
+ def query_all(selector)
166
+ `Array.from(document.querySelectorAll(#{selector}))`
167
+ end
168
+
169
+ # Get element by ID
170
+ def get_element_by_id(id)
171
+ `document.getElementById(#{id})`
172
+ end
173
+
174
+ # ===================
175
+ # Timing
176
+ # ===================
177
+
178
+ # Set timeout
179
+ def set_timeout(delay_ms, &block)
180
+ `setTimeout(#{block}, #{delay_ms})`
181
+ end
182
+
183
+ # Set interval
184
+ def set_interval(interval_ms, &block)
185
+ `setInterval(#{block}, #{interval_ms})`
186
+ end
187
+
188
+ # Clear timeout
189
+ def clear_timeout(timeout_id)
190
+ `clearTimeout(#{timeout_id})`
191
+ end
192
+
193
+ # Clear interval
194
+ def clear_interval(interval_id)
195
+ `clearInterval(#{interval_id})`
196
+ end
197
+
198
+ # ===================
199
+ # LocalStorage
200
+ # ===================
201
+
202
+ # Get from localStorage
203
+ def storage_get(key)
204
+ `localStorage.getItem(#{key})`
205
+ end
206
+
207
+ # Set to localStorage
208
+ def storage_set(key, value)
209
+ `localStorage.setItem(#{key}, #{value})`
210
+ end
211
+
212
+ # Remove from localStorage
213
+ def storage_remove(key)
214
+ `localStorage.removeItem(#{key})`
215
+ end
216
+
217
+ # ===================
218
+ # JSON
219
+ # ===================
220
+
221
+ # Parse JSON string
222
+ def parse_json(json_string)
223
+ `JSON.parse(#{json_string})`
224
+ end
225
+
226
+ # Stringify to JSON
227
+ def to_json_string(object)
228
+ `JSON.stringify(#{object})`
229
+ end
230
+
231
+ # ===================
232
+ # Type Conversion
233
+ # ===================
234
+
235
+ # Parse string to integer
236
+ def parse_int(value, radix = 10)
237
+ `parseInt(#{value}, #{radix})`
238
+ end
239
+
240
+ # Parse string to float
241
+ def parse_float(value)
242
+ `parseFloat(#{value})`
243
+ end
244
+
245
+ # Check if value is NaN
246
+ def is_nan?(value)
247
+ `Number.isNaN(#{value})`
248
+ end
249
+ end
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.2.5
4
+ version: 0.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - stofu1234
@@ -119,6 +119,7 @@ files:
119
119
  - opal/opal_vite/concerns/stimulus_helpers.rb
120
120
  - opal/opal_vite/concerns/storable.rb
121
121
  - opal/opal_vite/concerns/toastable.rb
122
+ - opal/opal_vite/concerns/vue_helpers.rb
122
123
  homepage: https://github.com/stofu1234/opal-vite
123
124
  licenses:
124
125
  - MIT