diff --git a/Rakefile b/Rakefile index 32ea1d432..ab61ae9dd 100644 --- a/Rakefile +++ b/Rakefile @@ -26,6 +26,10 @@ task :translatable_benchmark do ruby "./performance/translatable_benchmark.rb" end +task :slots_benchmark do + ruby "./performance/slots_benchmark.rb" +end + namespace :coverage do task :report do require "simplecov" diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 833a12b53..f27a00118 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,10 @@ nav_order: 5 ## main +* Ensure content is rendered correctly for forwarded slots. + + *Cameron Dutro* + ## 3.12.0 * Remove offline links from resources. diff --git a/lib/view_component/slot.rb b/lib/view_component/slot.rb index 8cda09896..205437040 100644 --- a/lib/view_component/slot.rb +++ b/lib/view_component/slot.rb @@ -57,7 +57,17 @@ def to_s if defined?(@__vc_content_block) # render_in is faster than `parent.render` - @__vc_component_instance.render_in(view_context, &@__vc_content_block) + @__vc_component_instance.render_in(view_context) do |*args| + return @__vc_content_block.call(*args) if @__vc_content_block&.source_location.nil? + + block_context = @__vc_content_block.binding.receiver + + if block_context.class < ActionView::Base + block_context.capture(*args, &@__vc_content_block) + else + @__vc_content_block.call(*args) + end + end else @__vc_component_instance.render_in(view_context) end diff --git a/performance/components/slots_wrapper_component.html.erb b/performance/components/slots_wrapper_component.html.erb new file mode 100644 index 000000000..5484c2fd3 --- /dev/null +++ b/performance/components/slots_wrapper_component.html.erb @@ -0,0 +1,3 @@ +<%= render(Performance::SlotsComponent.new(name: "Fox Mulder")) do |component| %> + <% component.with_header(classes: "foo") { "Header" } %> +<% end %> diff --git a/performance/components/slots_wrapper_component.rb b/performance/components/slots_wrapper_component.rb new file mode 100644 index 000000000..0509b61cc --- /dev/null +++ b/performance/components/slots_wrapper_component.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class Performance::SlotsWrapperComponent < ViewComponent::Base +end diff --git a/performance/slots_benchmark.rb b/performance/slots_benchmark.rb new file mode 100644 index 000000000..9c5d8aec9 --- /dev/null +++ b/performance/slots_benchmark.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Run `bundle exec rake benchmark` to execute benchmark. +# This is very much a work-in-progress. Please feel free to make/suggest improvements! + +require "benchmark/ips" + +# Configure Rails Environment +ENV["RAILS_ENV"] = "production" +require File.expand_path("../test/sandbox/config/environment.rb", __dir__) + +module Performance + require_relative "components/slots_component" + require_relative "components/slots_wrapper_component" +end + +class BenchmarksController < ActionController::Base +end + +BenchmarksController.view_paths = [File.expand_path("./views", __dir__)] +controller_view = BenchmarksController.new.view_context + +Benchmark.ips do |x| + x.time = 10 + x.warmup = 2 + + x.report("slots") do + controller_view.render(Performance::SlotsWrapperComponent.new(name: "Fox Mulder")) + end + + x.compare! +end diff --git a/test/sandbox/app/components/forwarding_slot_wrapper_component.html.erb b/test/sandbox/app/components/forwarding_slot_wrapper_component.html.erb new file mode 100644 index 000000000..4d1939750 --- /dev/null +++ b/test/sandbox/app/components/forwarding_slot_wrapper_component.html.erb @@ -0,0 +1,5 @@ +<%= render(ForwardingSlotComponent.new) do |panel| %> + <% panel.with_target_content do |header| %> + Target content + <% end %> +<% end %> diff --git a/test/sandbox/app/components/forwarding_slot_wrapper_component.rb b/test/sandbox/app/components/forwarding_slot_wrapper_component.rb new file mode 100644 index 000000000..0f49999b8 --- /dev/null +++ b/test/sandbox/app/components/forwarding_slot_wrapper_component.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class ForwardingSlotWrapperComponent < ViewComponent::Base +end + +unless defined?(ForwardingSlotComponent) + class ForwardingSlotComponent < ViewComponent::Base + def initialize + @target = TargetSlotComponent.new + end + + def with_target_content(&block) + @target.with_target_content(&block) + end + + def before_render + content + end + + def call + render(@target) + end + end + + class TargetSlotComponent < ViewComponent::Base + renders_one :target_content, "TargetContentComponent" + + def call + target_content + end + end + + class TargetContentComponent < ViewComponent::Base + def call + content + end + end +end diff --git a/test/sandbox/test/slotable_test.rb b/test/sandbox/test/slotable_test.rb index 6f3220ee6..164e6508d 100644 --- a/test/sandbox/test/slotable_test.rb +++ b/test/sandbox/test/slotable_test.rb @@ -758,4 +758,10 @@ def test_inline_html_escape_with_integer render_inline InlineIntegerComponent.new end end + + def test_forwarded_slot_renders_correctly + render_inline(ForwardingSlotWrapperComponent.new) + + assert_text "Target content", count: 1 + end end