Class: Rhales::LinkBasedInjectionDetector
- Inherits:
-
Object
- Object
- Rhales::LinkBasedInjectionDetector
- Defined in:
- lib/rhales/link_based_injection_detector.rb
Overview
Generates link-based hydration strategies that use browser resource hints and API endpoints instead of inline scripts
Supported Strategies
:link
- Basic link reference:<link href="/api/hydration/template">
:prefetch
- Background prefetch:<link rel="prefetch" href="..." as="fetch">
:preload
- High priority preload:<link rel="preload" href="..." as="fetch">
:modulepreload
- ES module preload:<link rel="modulepreload" href="..."
:lazy
- Lazy loading with intersection observer
All strategies generate both link tags and accompanying JavaScript for data fetching and assignment to window objects.
Instance Method Summary collapse
-
#generate_basic_link(template_name, window_attr, nonce) ⇒ Object
private
-
#generate_for_strategy(strategy, template_name, window_attr, nonce = nil) ⇒ Object
-
#generate_lazy_loading(template_name, window_attr, nonce) ⇒ Object
private
-
#generate_modulepreload_link(template_name, window_attr, nonce) ⇒ Object
private
-
#generate_prefetch_link(template_name, window_attr, nonce) ⇒ Object
private
-
#generate_preload_link(template_name, window_attr, nonce) ⇒ Object
private
-
#initialize(hydration_config) ⇒ LinkBasedInjectionDetector
constructor
A new instance of LinkBasedInjectionDetector.
-
#nonce_attribute(nonce) ⇒ Object
private
Constructor Details
#initialize(hydration_config) ⇒ LinkBasedInjectionDetector
Returns a new instance of LinkBasedInjectionDetector.
19 20 21 22 23 |
# File 'lib/rhales/link_based_injection_detector.rb', line 19 def initialize(hydration_config) @hydration_config = hydration_config @api_endpoint_path = hydration_config.api_endpoint_path || '/api/hydration' @crossorigin_enabled = hydration_config.link_crossorigin.nil? ? true : hydration_config.link_crossorigin end |
Instance Method Details
#generate_basic_link(template_name, window_attr, nonce) ⇒ Object (private)
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/rhales/link_based_injection_detector.rb', line 44 def generate_basic_link(template_name, window_attr, nonce) endpoint_url = "#{@api_endpoint_path}/#{template_name}" link_tag = %(<link href="#{endpoint_url}" type="application/json">) script_tag = <<~HTML.strip <script#{nonce_attribute(nonce)} data-hydration-target="#{window_attr}"> // Load hydration data for #{window_attr} window.__rhales__ = window.__rhales__ || {}; if (!window.__rhales__.loadData) { window.__rhales__.loadData = function(target, url) { fetch(url) .then(r => r.json()) .then(data => window[target] = data); }; } window.__rhales__.loadData('#{window_attr}', '#{endpoint_url}'); </script> HTML "#{link_tag}\n#{script_tag}" end |
#generate_for_strategy(strategy, template_name, window_attr, nonce = nil) ⇒ Object
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/rhales/link_based_injection_detector.rb', line 25 def generate_for_strategy(strategy, template_name, window_attr, nonce = nil) case strategy when :link generate_basic_link(template_name, window_attr, nonce) when :prefetch generate_prefetch_link(template_name, window_attr, nonce) when :preload generate_preload_link(template_name, window_attr, nonce) when :modulepreload generate_modulepreload_link(template_name, window_attr, nonce) when :lazy generate_lazy_loading(template_name, window_attr, nonce) else raise ArgumentError, "Unsupported link strategy: #{strategy}" end end |
#generate_lazy_loading(template_name, window_attr, nonce) ⇒ Object (private)
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/rhales/link_based_injection_detector.rb', line 137 def generate_lazy_loading(template_name, window_attr, nonce) endpoint_url = "#{@api_endpoint_path}/#{template_name}" mount_selector = @hydration_config.lazy_mount_selector || '#app' # No link tag for lazy loading - purely script-driven script_tag = <<~HTML.strip <script#{nonce_attribute(nonce)} data-hydration-target="#{window_attr}" data-lazy-src="#{endpoint_url}"> // Lazy loading strategy with intersection observer window.__rhales__ = window.__rhales__ || {}; window.__rhales__.initLazyLoading = function() { const mountElement = document.querySelector('#{mount_selector}'); if (!mountElement) { console.warn('Rhales: Mount element "#{mount_selector}" not found for lazy loading'); return; } const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { fetch('#{endpoint_url}') .then(r => r.json()) .then(data => { window['#{window_attr}'] = data; window.dispatchEvent(new CustomEvent('rhales:hydrated', { detail: { target: '#{window_attr}', data: data } })); }) .catch(err => console.error('Rhales lazy hydration error:', err)); observer.unobserve(entry.target); } }); }); observer.observe(mountElement); }; // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', window.__rhales__.initLazyLoading); } else { window.__rhales__.initLazyLoading(); } </script> HTML script_tag end |
#generate_modulepreload_link(template_name, window_attr, nonce) ⇒ Object (private)
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/rhales/link_based_injection_detector.rb', line 116 def generate_modulepreload_link(template_name, window_attr, nonce) endpoint_url = "#{@api_endpoint_path}/#{template_name}.js" link_tag = %(<link rel="modulepreload" href="#{endpoint_url}">) script_tag = <<~HTML.strip <script type="module"#{nonce_attribute(nonce)} data-hydration-target="#{window_attr}"> // Module preload strategy import data from '#{endpoint_url}'; window['#{window_attr}'] = data; // Dispatch ready event window.dispatchEvent(new CustomEvent('rhales:hydrated', { detail: { target: '#{window_attr}', data: data } })); </script> HTML "#{link_tag}\n#{script_tag}" end |
#generate_prefetch_link(template_name, window_attr, nonce) ⇒ Object (private)
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/rhales/link_based_injection_detector.rb', line 67 def generate_prefetch_link(template_name, window_attr, nonce) endpoint_url = "#{@api_endpoint_path}/#{template_name}" crossorigin_attr = @crossorigin_enabled ? ' crossorigin' : '' link_tag = %(<link rel="prefetch" href="#{endpoint_url}" as="fetch"#{crossorigin_attr}>) script_tag = <<~HTML.strip <script#{nonce_attribute(nonce)} data-hydration-target="#{window_attr}"> // Prefetch hydration data for #{window_attr} window.__rhales__ = window.__rhales__ || {}; if (!window.__rhales__.loadPrefetched) { window.__rhales__.loadPrefetched = function(target, url) { fetch(url) .then(r => r.json()) .then(data => window[target] = data); }; } window.__rhales__.loadPrefetched('#{window_attr}', '#{endpoint_url}'); </script> HTML "#{link_tag}\n#{script_tag}" end |
#generate_preload_link(template_name, window_attr, nonce) ⇒ Object (private)
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/rhales/link_based_injection_detector.rb', line 91 def generate_preload_link(template_name, window_attr, nonce) endpoint_url = "#{@api_endpoint_path}/#{template_name}" crossorigin_attr = @crossorigin_enabled ? ' crossorigin' : '' link_tag = %(<link rel="preload" href="#{endpoint_url}" as="fetch"#{crossorigin_attr}>) script_tag = <<~HTML.strip <script#{nonce_attribute(nonce)} data-hydration-target="#{window_attr}"> // Preload strategy - high priority fetch fetch('#{endpoint_url}') .then(r => r.json()) .then(data => { window['#{window_attr}'] = data; // Dispatch ready event window.dispatchEvent(new CustomEvent('rhales:hydrated', { detail: { target: '#{window_attr}', data: data } })); }) .catch(err => console.error('Rhales hydration error:', err)); </script> HTML "#{link_tag}\n#{script_tag}" end |
#nonce_attribute(nonce) ⇒ Object (private)
186 187 188 189 |
# File 'lib/rhales/link_based_injection_detector.rb', line 186 def nonce_attribute(nonce) require 'erb' nonce ? " nonce=\"#{ERB::Util.html_escape(nonce)}\"" : '' end |