Class: SvelteOnRails::Lib::ViewHelperSupport

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

Constant Summary collapse

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, caching = false) ⇒ ViewHelperSupport

Returns a new instance of ViewHelperSupport.



10
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
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 10

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

  @start_time = Time.now
  @conf = SvelteOnRails::Configuration.instance
  @utils = SvelteOnRails::Lib::Utils
  @options = options
  @component_name = component
  @view_dir = view_dir
  @request = request
  @props = props
  @html_options = html_options

  @ssr = if request == :cache_warmer
           true
         else
           resolve_ssr?(request)
         end
  return unless @ssr

  if @conf.watch_changes?

    # check file existence and capitalization

    p = component_paths
    unless File.exist?(Rails.root.join(p[:path]))
      raise <<~TEXT
      [SOR] Source file not found: «#{p[:path]}»

      TEXT
    end
    if p[:path][0..9] == 'app/views/'
      if !p[:file_basename].match?(/^[A-Z][A-Za-z0-9]+$/) && !p[:file_basename].match?(/^[a-z0-9][a-z0-9_]+$/)
        raise <<~TEXT
        [SOR] Invalid filename: «#{p[:file_basename]}»
        Component names must be CamelCased or under_scored and must be prefixed by underscore.

        TEXT
      end
    elsif !p[:file_basename].match?(/^[A-Z][A-Za-z0-9]+$/)
      raise <<~TEXT
      [SOR] Invalid filename: «#{p[:file_basename]}»
      Component names must be CamelCased.
      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 importand 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 dependend source files,
    # check theyr 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 @conf.configs[:cache_warmer] && caching
    Thread.new do
      SvelteOnRails::Lib::CacheWarmer.instance.store_request(component, props, html_options, options, view_dir)
    end
  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.



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

def conf
  @conf
end

Instance Method Details

#component_pathsObject



112
113
114
115
116
117
118
119
120
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 112

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)


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

def debug?
  @options[:debug]
end

#log_completed(message) ⇒ Object



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

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

#render_cached(view_context, &block) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 162

def render_cached(view_context, &block)

  # increase expired time

  @conf.redis_instance.expire(cache_key, redis_expiration_seconds)

  # render

  res = if @cached_content
          msg = "returned from cache"
          @cached_content.html_safe
        else
          msg = "Rendered"
          @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
  log_completed(msg)
  res

end

#request_metricsObject



189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 189

def request_metrics
  @conf.request_metrics ||= {}
  _req_id = @conf.request_metrics[:request_uuid]

  if @request.uuid != _req_id
    @conf.request_metrics = {
      request_uuid: @request.uuid,
      total_time: 0.0
    }
  end

  @conf.request_metrics
end

#set_request_metrics(action_key) ⇒ Object



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

def set_request_metrics(action_key)

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

  request_metrics

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

#ssr?Boolean

Returns:

  • (Boolean)


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

def ssr?
  @ssr
end

#tag_attributes(html_options, svelte_props) ⇒ Object



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

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'
      }
    }
  )
  cl = "svelte-component #{ht_opts[:class]}".split(' ')
  _cl = 'svelte-' + component_paths[:name].underscore.gsub('_', '-')
  cl.push(_cl) unless cl.include?(_cl)


  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'
  #svelte_props = {my: :joke}

  ht_opts[:class] = cl.compact.join(' ')
  ht_opts[:data][:props] = svelte_props.to_json
  ht_opts[:data][:svelte_status] = 'do-not-hydrate-me' if @options[:hydrate] == false
  ht_opts
end