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