lib/svelte_on_rails/lib/view_helper_support.rb
# lib/svelte_on_rails/view_helper_support.rb module SvelteOnRails module Lib class ViewHelperSupport attr_reader :filename, :helper_options, :html_options, :request, :conf def initialize(file, props, request, caching = false) @start_time = Time.now @filename = (file.match(/\.svelte$/) ? file[0..-8] : file) @conf = SvelteOnRails::Configuration.instance @all_arguments = props @helper_options, @html_options, @props, @props_json = split_props( props, %i[class id style], %i[ssr hydrate debug cache_key expires_in] ) @request = request @ssr = determine_ssr validate_file if @conf.watch_changes? # precompile if !Dir.exist?(@conf.ssr_dist_folder) || @conf.watch_changes? SvelteOnRails::Lib::Utils.watch_changes_and_precompile end # caching if caching raise '[svelte-on-rails] Caching required but Redis is not defined' unless defined?(Redis) else raise '[svelte-on-rails] :expires_in is not allowed for this helper' if @helper_options.key?(:expires_in) raise '[svelte-on-rails] :cache_key is not allowed for this helper' if @helper_options.key?(:cache_key) return end return unless ssr? generate_cache_key end def debug? @helper_options[:debug] end def redis_expiration_seconds (conf.redis_cache_store[:expires_in] || @helper_options[:expires_in] || 1.hour).to_i end def elapsed_milliseconds ((Time.now - @start_time) * 1000).round(1) end def cache_key_primary @cache_key_primary end def cache_key @cache_key end def ssr? @ssr end def file_not_found_message ff = conf.frontend_folder cf = conf.components_folder "svelte-on-rails gem, view helper #svelte_component\n\nFile not found:\n" + "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" + "Your configurations are:\n\nfrontend_folder: «#{ff}»\ncomponents_folder: «#{cf}»\n.. and the filename attribute for the view helper: «#{filename}»\n" end def file_case_sensitive_message ff = conf.frontend_folder cf = conf.components_folder "svelte-on-rails gem, view helper #svelte_component\n\n" + "File found but Upper and lower case letters are incorrect:\n" + "(This check is only on development environments when watch_changes is true)\n\n" + "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" + "Your configurations are:\nfrontend_folder: «#{ff}»\ncomponents_folder: «#{cf}»\n.. and the filename attribute for the view helper: «#{filename}»\n" end def log_rendering(message) Rails.logger.info " #{message} (#{elapsed_milliseconds}ms)" end def debug_log(message) if debug? Rails.logger.debug(" [svelte_component] #{message} (#{elapsed_milliseconds} ms)") end end def render_ssr renderer = SvelteOnRails::Renderer.new(filename) renderer.render(@props) end private def validate_file file_path = conf.components_folder_full + "#{filename}.svelte" unless File.exist?(file_path) raise file_not_found_message end if conf.watch_changes? && !SvelteOnRails::Lib::Utils.file_exist_case_sensitive?(conf.components_folder_full, "#{filename}.svelte") raise file_case_sensitive_message end end def determine_ssr _ssr = if @helper_options.key?(:ssr) if @conf.watch_changes? unless [true, false, :auto].include?(@helper_options[:ssr]) raise "Only true, false, or :auto are allowed for the argument #ssr" end end @helper_options[:ssr] else @conf.ssr end _ssr == :auto ? request.headers[conf.non_ssr_request_header].blank? : _ssr end def generate_cache_key mtime_file = @conf.ssr_dist_folder.join('last_mtime') mtime = File.read(mtime_file) key2 = if @helper_options[:cache_key] k2 = (@helper_options[:cache_key]) keys = k2.is_a?(Array) ? k2 : [k2] keys.map { |k| k.is_a?(ActiveRecord::Base) ? "#{k.class.name}#{k.id}" : k.to_s }.join('-') end filename_part = [ "#{filename.split('/').last}.svelte", Zlib.crc32(filename).to_s(36), key2 ].compact.join('-') @cache_key_primary = [ conf.redis_cache_store[:namespace] ? conf.redis_cache_store[:namespace] : "svelte-on-rails:#{Rails.env}", filename_part, ].join(':') last_part = [ (@conf.watch_changes? ? Zlib.crc32(mtime).to_s(36) : nil), Zlib.crc32(@all_arguments.to_json).to_s(36) ].compact.join('-') @cache_key = [@cache_key_primary, last_part].join(':') end def split_props(props, html_options, helper_options) prp = {} hlp_opts = {} ht_opts = { data: { svelte_component: "/#{conf.components_folder + filename}", controller: 'svelte-on-rails', } } props.each do |k, v| _k = k.to_sym if helper_options.include?(_k) hlp_opts[_k] = v elsif html_options.include?(_k) ht_opts[_k] = v else prp[_k] = v end end ht_opts[:class] = "#{ht_opts[:class]} svelte-component".strip prp_js = prp.to_json ht_opts[:data][:props] = prp_js ht_opts[:data][:svelte_status] = 'do-not-hydrate-me' if hlp_opts[:hydrate] == false [hlp_opts, ht_opts, prp, prp_js] end end end end