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_PLACEHOLDER_VDKVUKCYPTHQ4R6Z4EJ**"
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
# 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
  @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][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 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

#component_pathsObject



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

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)


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

def debug?
  @options[:debug]
end

#log_completed(message) ⇒ Object



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

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

#propsObject



211
212
213
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 211

def props
  @props ||= {}
end

#render_cached(view_context, &block) ⇒ Object



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

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



187
188
189
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 187

def request_metrics
  @conf.request_metrics
end

#set_request_metrics(action_key) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 191

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"
  end

  request_metrics

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

end

#ssr?Boolean

Returns:

  • (Boolean)


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

def ssr?
  @ssr
end

#tag_attributes(html_options, svelte_props) ⇒ Object



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

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