lib/amber_component/rendering.rb in amber_component-1.1.1 vs lib/amber_component/rendering.rb in amber_component-1.2.0

- old
+ new

@@ -1,32 +1,94 @@ # frozen_string_literal: true module ::AmberComponent # Provides universal methods for rendering components. module Rendering + + # @return [Symbol] + RENDER_TEMPLATE_METHOD_NAME = :__render + # Class methods for rendering. module ClassMethods # @param kwargs [Hash{Symbol => Object}] # @return [String] def render(**kwargs, &block) - comp = new(**kwargs) - - comp.render(&block) + new(**kwargs).render(&block) end alias call render + + # @return [Boolean] + def compiled? + method_defined?(RENDER_TEMPLATE_METHOD_NAME) + end + + # @return [Boolean] + def compile? + return true if defined?(::Rails.env) && ::Rails.env.development? + + !compiled? + end + + # @param force [Boolean] force recompilation + # @return [void] + def compile(force: false) + return if !compile? && !force + return if template_handler.nil? + + render_template_method_redefinition_lock.synchronize do + silence_redefinition_of_method(RENDER_TEMPLATE_METHOD_NAME) + # rubocop:disable Style/EvalWithLocation + class_eval <<~CODE, view_path.to_s, 0 # rubocop:disable Style/DocumentDynamicEvalDefinition + def #{RENDER_TEMPLATE_METHOD_NAME}(local_assigns, output_buffer, &block) + #{compiled_template_source} + end + CODE + # rubocop:enable Style/EvalWithLocation + end + end + + # @return [Class, nil] + def template_handler + @template_handler ||= ::ActionView::Template.registered_template_handler(view_type) + end + + private + + # @return [Mutex] + def render_template_method_redefinition_lock + @render_template_method_redefinition_lock ||= ::Mutex.new + end + + # @return [String] + def compiled_template_source + handler = template_handler + unless handler + raise UnknownViewTypeError, + "view type `#{view_type.inspect}` is not known in #{self}, " \ + "available types: #{::ActionView::Template.template_handler_extensions.inspect}" + end + + if handler.method(:call).parameters.length > 1 + handler.call(self, view_template_source) + else + handler.call( + source: view_template_source, + identifier: identifier, + type: type + ) + end + end + end # Instance methods for rendering. module InstanceMethods # @return [String] def render(&block) run_callbacks :render do - element = render_view(&block) - # styles = inject_styles - # element += styles unless styles.nil? - element.html_safe + compile_and_render(&block) end end # Method used internally by Rails to render an object # passed to the `render` method. @@ -37,16 +99,52 @@ # @return [String] def render_in(_context) render end - protected + # @param args [Array<Object>] + # @return [String] + def nested_content(*args, &block) + block_self = block.binding.receiver + return block_self.safe_capture(*args, &block) if block_self.respond_to?(:safe_capture) + safe_capture(*args, &block) + end + alias children nested_content + + def safe_capture(*args) + value = nil + buffer = with_output_buffer { value = yield(*args) } + buffer.presence || value.html_safe + end + + private + + # @return [String] + def compile_and_render(&block) + self.class.compile + if self.class.compiled? + return _run( + RENDER_TEMPLATE_METHOD_NAME, + self.class.template_handler, + [], + ::ActionView::OutputBuffer.new, + &block + ) + end + + render_non_rails_string( + self.class.view_template_source, + self.class.view_type, + block + ) + end + # @param content [String] # @param type [Symbol] # @param block [Proc, nil] # @return [String] - def render_string(content, type, block = nil) + def render_non_rails_string(content, type, block = nil) TemplateHandler.render_from_string(self, content, type, block) end end end end