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: [:oneof, [true, false, :auto]],
  hydrate: [:oneof, [true, false]],
  debug: [:oneof, [true, false]],
  cache_key: [:typeof, [String, Array]],
  cache_expires_in: [:typeof, [Integer, ActiveSupport::Duration]],
  cached: [:oneof, [true, false]]
}

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.



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
115
116
117
118
119
120
121
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 18

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.



16
17
18
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 16

def conf
  @conf
end

Instance Method Details

#cached_propsObject



234
235
236
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 234

def cached_props
  @cached_props ||= {}
end

#component_pathsObject



123
124
125
126
127
128
129
130
131
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 123

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)


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

def debug?
  @options[:debug]
end

#log_completed(message) ⇒ Object



162
163
164
165
166
167
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 162

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



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

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, ex: redis_expiration_seconds)
            end
            r
          end
        end
  res

end

#render_svelte(current_template) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 169

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



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

def request_metrics
  @conf.request_metrics
end

#set_request_metrics(action_key) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 214

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)


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

def ssr?
  @ssr
end

#tag_attributes(html_options, svelte_props) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/svelte_on_rails/lib/view_helper_support.rb', line 133

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