Class: SvelteOnRails::Lib::ViewHelperSupport

Inherits:
Object
  • Object
show all
Defined in:
lib/svelte_on_rails/lib/view_helper_support.rb

Constant Summary collapse

UNCACHED_PROPS_PLACEHOLDER =
"**Uncached Props are available only after hydration**"
RENDER_OPTIONS =
{ ssr: [true, false, :auto], hydrate: :bool, debug: :bool, cache_key: :string_array, expires_in: :integer, cached: :bool }

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(component, props, html_options, options, request, view_dir, config, caching = false, start_time) ⇒ ViewHelperSupport

Returns a new instance of ViewHelperSupport.



11
12
13
14
15
16
17
18
19
20
21
22
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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/svelte_on_rails/lib/view_helper_support.rb', line 11

def initialize(
  component, props, html_options, options,
  request, view_dir, config, caching = false, start_time
)

  @start_time = start_time
  @conf = config
  @conf.initialize_request_metrics(request.uuid)

  @utils = SvelteOnRails::Lib::Utils
  @options = options
  @component_name = component
  @view_dir = view_dir
  @request = request
  @cached_props = if props.key?(:_uncached)
                    props.merge(_uncached: UNCACHED_PROPS_PLACEHOLDER)
                  else
                    props
                  end
  @html_options = html_options

  @ssr = resolve_ssr?(request)
  return unless @ssr

  if @conf.watch_changes?

    # check file existence and capitalization

    p = component_paths
    if !p[:path] && p[:failed_attempts]
      raise <<~TEXT
        [Svelte on Rails] Component source file not found, searched by:
#{p[:failed_attempts].join("\n  • ")}
      TEXT
    elsif !File.exist?(Rails.root.join(p[:path]))
      raise <<~TEXT
        [Svelte on Rails] Source file not found: «#{p[:path]}»
      TEXT
    end
    if p[:path].start_with?('app/views/')
      if !p[:file_basename].match?(/^[A-Z][A-Za-z0-9]*$/) && !p[:file_basename].match?(/^_[a-z0-9][a-z0-9_]*$/)
        @utils.puts_warning <<~TEXT
              Invalid filename: «#{p[:file_basename]}»
              Component names should be CamelCased, or under_scored and prefixed by an underscore (e.g. «_my_partial»).
              See: https://svelte-on-rails.dev/naming.html
            TEXT
      end
    elsif !p[:file_basename].match?(/^[A-Z][A-Za-z0-9]*$/)
      @utils.puts_warning <<~TEXT
            Invalid filename: «#{p[:file_basename]}»
            Component names should be CamelCased.
            See: https://svelte-on-rails.dev/naming.html
          TEXT
    end
    full_path = Rails.root.join(p[:path])
    dir, wanted = full_path.dirname.to_s, full_path.basename.to_s
    unless Dir.children(dir).include?(wanted)
      raise "ERROR: Incorrect capitalisation: «#{p[:path]}»"
    end

    if !@conf.manifest_json_path.exist? ||
       !@conf.dev_module_map_path.exist? ||

       @conf.build_status['mtime'].to_f != @utils.manifest_file_mtime ||
      # if a developer has run build from the console, we must update the build-status object
      # this is importand for having reliable error-messages

       (!@conf.build_status['passed'] && !request_metrics[:build_tried_for_error_message])
      # when build has failed the developer sees the error message
      # at this point it is important to see the build-error in the logs
      # but only ONCE for a request, so we check metrics too

      run_vite_build
      set_request_metrics(:build_tried_for_error_message)

      return unless @conf.build_status['passed']
    end

    # now we are sure to have a valid manifest
    # and are able to collect the dependent source files,
    # check their mtime
    # and run a build if something has changed (is only the case if the build above not was run)
    ensure_valid_component_and_manifest!(component)
    if @conf.component_mtime_changed?(component_paths[:path], debug?, component_paths[:file_basename])
      run_vite_build
    end

    # finally, validate the given options
    validate_options

  end

  if caching
    @cached_content = @conf.debug_log(debug?, component_paths[:name], "Fetch Cache") do
      ck = (@conf.watch_changes? ? cache_key_fresh : cache_key)
      @conf.redis_instance.get(ck)
    end
    return if @cached_content
  elsif @conf.watch_changes?
    raise '[SOR] :expires_in is not allowed for this helper' if @options.key?(:expires_in)
    raise '[SOR] :cache_key is not allowed for this helper' if @options.key?(:cache_key)
  end

end

Instance Attribute Details

#confObject (readonly)

Returns the value of attribute conf.



9
10
11
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 9

def conf
  @conf
end

Instance Method Details

#cached_propsObject



227
228
229
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 227

def cached_props
  @cached_props ||= {}
end

#component_pathsObject



116
117
118
119
120
121
122
123
124
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 116

def component_paths
  if @conf.watch_changes?
    @component_paths ||= @utils.component_paths_uncached(@component_name, @view_dir)
  else
    key = "#{@component_name}_#{@view_dir}"
    @conf.component_paths_cache[key] ||= @utils.component_paths_uncached(@component_name, @view_dir)
  end

end

#debug?Boolean

Returns:

  • (Boolean)


147
148
149
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 147

def debug?
  @options[:debug]
end

#log_completed(message) ⇒ Object



155
156
157
158
159
160
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 155

def log_completed(message)
  ms = ((Time.now - @start_time) * 1000)
  @conf.request_metrics[:total_time] += ms
  msg = "  [SOR] #{component_paths[:name]}.svelte #{message} (#{ms.round(1)}ms)"
  Rails.logger.info msg
end

#render_cached(view_context, &block) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 179

def render_cached(view_context, &block)

  # increase expired time

  @conf.redis_instance.expire(cache_key, redis_expiration_seconds)

  # render

  res = if @cached_content
          @cached_content.html_safe
        else
          @skip_caching = false
          @conf.debug_log(debug?, component_paths[:name], "rendered and stored to cache (key: «#{cache_key}»)") do
            r = view_context.instance_eval(&block)
            unless @skip_caching
              @conf.redis_instance.set(cache_key, r)
            end
            r
          end
        end
  res

end

#render_svelte(current_template) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 162

def render_svelte(current_template)
  caller_loc = Proc.new do
    v_path = current_template.identifier
    caller_locations.find { |loc| loc.to_s.include?(v_path) }.to_s.match(/^[\s\S]+(:[0-9]+(?=:in))/).to_s
  end

  r = render_with_ssr_server(caller_loc) || render_with_fallback

  if r[:success]
    set_request_metrics(:rendered)
  else
    @skip_caching = true
  end

  r
end

#request_metricsObject



203
204
205
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 203

def request_metrics
  @conf.request_metrics
end

#set_request_metrics(action_key) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 207

def set_request_metrics(action_key)

  if @conf.watch_changes? &&
     ![
       :build,
       :from_cache,
       :empty,
       :rendered,
       :build_tried_for_error_message
     ].include?(action_key)
    raise "[SOR] set_request_metrics: invalid action_key: #{action_key}"
  end

  request_metrics

  @conf.request_metrics[action_key] ||= 0
  @conf.request_metrics[action_key] += 1

end

#ssr?Boolean

Returns:

  • (Boolean)


151
152
153
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 151

def ssr?
  @ssr
end

#tag_attributes(html_options, svelte_props) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 126

def tag_attributes(html_options, svelte_props)
  ht_opts = html_options.deep_symbolize_keys.deep_merge(
    {
      data: {
        component: component_paths[:vite_path],
        controller: 'svelte-on-rails'
      }
    }
  )

  tnl = SvelteOnRails::Lib::ToSvelteTranslations.instance
  _tnl = tnl.cached_translations(I18n.locale, :to_svelte_props, nil)
  svelte_props[:_translations] = _tnl if _tnl
  svelte_props[:test] = 'hello'

  ht_opts[:class] = "svelte-component #{ht_opts[:class]}"
  ht_opts[:data][:props] = svelte_props.to_json
  ht_opts[:data][:svelte_status] = (@options[:hydrate] == false ? 'do-not-hydrate-me' : 'ssr')
  ht_opts
end