Class: Rhales::MountPointDetector
- Inherits:
-
Object
- Object
- Rhales::MountPointDetector
- Defined in:
- lib/rhales/mount_point_detector.rb
Overview
Detects frontend application mount points in HTML templates Used to determine optimal hydration script injection points
Mount Point Detection Order
- Selector Priority: All selectors (default + custom) are checked in parallel
- Position Priority: Returns the earliest mount point by position in HTML (not selector order)
- Safety Validation: Validates injection points are outside unsafe contexts (scripts/styles/comments)
- Safe Position Search: If original position unsafe, searches for nearest safe alternative:
- First tries positions before the mount point (maintains earlier injection)
- Then tries positions after the mount point (fallback)
- Returns nil if no safe position found
Default selectors are checked: [‘#app’, ‘#root’, ‘[data-rsfc-mount]’, ‘[data-mount]’] Custom selectors can be added via configuration and are combined with defaults.
Constant Summary collapse
- DEFAULT_SELECTORS =
['#app', '#root', '[data-rsfc-mount]', '[data-mount]'].freeze
Instance Method Summary collapse
-
#build_pattern(selector) ⇒ Object
private
-
#detect(template_html, custom_selectors = []) ⇒ Object
-
#find_safe_injection_position(validator, preferred_position) ⇒ Object
private
-
#find_tag_start(scanner, template_html) ⇒ Object
private
Instance Method Details
#build_pattern(selector) ⇒ Object (private)
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/rhales/mount_point_detector.rb', line 57 def build_pattern(selector) case selector when /^#(.+)$/ # ID selector: <tag id="value"> id_name = Regexp.escape($1) /id\s*=\s*["']#{id_name}["']/i when /^\.(.+)$/ # Class selector: <tag class="... value ..."> class_name = Regexp.escape($1) /class\s*=\s*["'][^"']*\b#{class_name}\b[^"']*["']/i when /^\[([^\]]+)\]$/ # Attribute selector: <tag data-attr> or <tag data-attr="value"> attr_name = Regexp.escape($1) /#{attr_name}(?:\s*=\s*["'][^"']*["'])?/i else # Invalid selector, match nothing /(?!.*)/ end end |
#detect(template_html, custom_selectors = []) ⇒ Object
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/rhales/mount_point_detector.rb', line 23 def detect(template_html, custom_selectors = []) selectors = (DEFAULT_SELECTORS + Array(custom_selectors)).uniq scanner = StringScanner.new(template_html) validator = SafeInjectionValidator.new(template_html) mount_points = [] selectors.each do |selector| scanner.pos = 0 pattern = build_pattern(selector) while scanner.scan_until(pattern) # Calculate position where the full tag starts tag_start_pos = find_tag_start(scanner, template_html) # Only include mount points that are safe for injection safe_position = find_safe_injection_position(validator, tag_start_pos) if safe_position mount_points << { selector: selector, position: safe_position, original_position: tag_start_pos, matched: scanner.matched } end end end # Return earliest mount point by position mount_points.min_by { |mp| mp[:position] } end |
#find_safe_injection_position(validator, preferred_position) ⇒ Object (private)
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/rhales/mount_point_detector.rb', line 89 def find_safe_injection_position(validator, preferred_position) # First check if the preferred position is safe return preferred_position if validator.safe_injection_point?(preferred_position) # Try to find a safe position before the preferred position safe_before = validator.nearest_safe_point_before(preferred_position) return safe_before if safe_before # As a last resort, try after the preferred position safe_after = validator.nearest_safe_point_after(preferred_position) return safe_after if safe_after # No safe position found nil end |
#find_tag_start(scanner, template_html) ⇒ Object (private)
77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/rhales/mount_point_detector.rb', line 77 def find_tag_start(scanner, template_html) # Work backwards from current position to find the opening < pos = scanner.pos - scanner.matched.length while pos > 0 && template_html[pos - 1] != '<' pos -= 1 end # Return position of the < character pos > 0 ? pos - 1 : 0 end |