view_component 2.50.0 → 2.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of view_component might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ea90ad2ed037528865b7e6cf8b5850df938b624883a1508d85de52d8978c4fb
4
- data.tar.gz: f2c1e558a700b9f79b3939785f3ff6c2f812a6e03b697f9b60f4aa6f6eaf54a0
3
+ metadata.gz: 0d505afaa4cb16810628e814c69bb2d00bc76498f6c681173d7811f44cd87c8e
4
+ data.tar.gz: 2bc859d6034fed19e34739cf1844679bcf83332c7e227c487ddb7364e8166f4b
5
5
  SHA512:
6
- metadata.gz: 9fd97f786e4382098f81e5ec6230b6ae550991641f83d74ecfd67d3eacea515cd899ab21264c8ac5b5de1caed7f35304eb9b6cba13f092e5abdb3a6dc45f6b06
7
- data.tar.gz: b70d4f308f47c6937d8aa2e518564ebbc6313366168e6382f4c296823ed07489c7be333f8f47822e65d3a5159753b5d3934ead8cd4323c3777d0c22dde9870f8
6
+ metadata.gz: 75dcf204fd8e3dfad8a1da18de6d96d43fd779f1c9a73ca66b76f0db1ac7be04cf04447c9eaa901d32bc674d7eb7fa79fd5572bff08652a892a7e96f5186faaa
7
+ data.tar.gz: 7be68adaa9de981a4fb12451ebcc19875f5754fa4798eef60f98702b1c7396bb598a1f6ae77b768bbe55e5b4f77c33f6b9db7156fa18f04c0a63075c54fdd7cc
data/docs/CHANGELOG.md CHANGED
@@ -3,10 +3,75 @@ layout: default
3
3
  title: Changelog
4
4
  ---
5
5
 
6
+ <!-- Add unreleased changes under the "main" heading. -->
7
+
6
8
  # Changelog
7
9
 
8
10
  ## main
9
11
 
12
+ ## 2.53.0
13
+
14
+ * Add support for relative I18n scopes to translations.
15
+
16
+ *Elia Schito*
17
+
18
+ * Update CI configuration to use latest Rails 7.0.
19
+
20
+ *Hans Lemuet*
21
+
22
+ * Document how to use blocks with lambda slots.
23
+
24
+ *Sam Partington*
25
+
26
+ * Skip Rails 5.2 in local test environment if using incompatible Ruby version.
27
+
28
+ *Cameron Dutro*, *Blake Williams*, *Joel Hawksley*
29
+
30
+ * Improve landing page documentation.
31
+
32
+ *Jason Swett*
33
+
34
+ * Add Bearer to list of companies that heavily rely on ViewComponent.
35
+
36
+ *Yaroslav Shmarov*
37
+
38
+ * Add articles to resources page.
39
+
40
+ *Joel Hawksley*
41
+
42
+ ## 2.52.0
43
+
44
+ * Add ADR for separate slot getter/setter API.
45
+
46
+ *Blake Williams*
47
+
48
+ * Add the option to use a "global" output buffer so `form_for` and friends can be used with view components.
49
+
50
+ *Cameron Dutro*, *Blake Williams*
51
+
52
+ * Fix fragment caching in partials when global output buffer is enabled.
53
+ * Fix template inheritance when eager loading is disabled.
54
+
55
+ *Cameron Dutro*
56
+
57
+ ## 2.51.0
58
+
59
+ * Update the docs only when releasing a new version.
60
+
61
+ *Hans Lemuet*
62
+
63
+ * Alphabetize companies using ViewComponent and add Brightline to the list.
64
+
65
+ *Jack Schuss*
66
+
67
+ * Add CMYK value for ViewComponent Red color on logo page.
68
+
69
+ *Dylan Smith*
70
+
71
+ * Improve performance by moving template compilation from `#render_in` to `#render_template_for`.
72
+
73
+ *Cameron Dutro*
74
+
10
75
  ## 2.50.0
11
76
 
12
77
  * Add tests for `layout` usage when rendering via controller.
@@ -91,7 +156,7 @@ title: Changelog
91
156
 
92
157
  *Joel Hawksley*
93
158
 
94
- * Add Ruby 3.1 and Rails 7.0 to CI
159
+ * Add Ruby 3.1 and Rails 7.0 to CI.
95
160
 
96
161
  *Peter Goldstein*
97
162
 
@@ -66,11 +66,11 @@ module ViewComponent
66
66
  #
67
67
  # @return [String]
68
68
  def render_in(view_context, &block)
69
- self.class.compile(raise_errors: true)
70
-
71
69
  @view_context = view_context
72
70
  self.__vc_original_view_context ||= view_context
73
71
 
72
+ @output_buffer = ActionView::OutputBuffer.new unless @global_buffer_in_use
73
+
74
74
  @lookup_context ||= view_context.lookup_context
75
75
 
76
76
  # required for path helpers in older Rails versions
@@ -104,7 +104,7 @@ module ViewComponent
104
104
  before_render
105
105
 
106
106
  if render?
107
- render_template_for(@__vc_variant).to_s + _output_postamble
107
+ perform_render
108
108
  else
109
109
  ""
110
110
  end
@@ -112,6 +112,20 @@ module ViewComponent
112
112
  @current_template = old_current_template
113
113
  end
114
114
 
115
+ def perform_render
116
+ render_template_for(@__vc_variant).to_s + _output_postamble
117
+ end
118
+
119
+ # :nocov:
120
+ def render_template_for(variant = nil)
121
+ # Force compilation here so the compiler always redefines render_template_for.
122
+ # This is mostly a safeguard to prevent infinite recursion.
123
+ self.class.compile(raise_errors: true, force: true)
124
+ # .compile replaces this method; call the new one
125
+ render_template_for(variant)
126
+ end
127
+ # :nocov:
128
+
115
129
  # EXPERIMENTAL: Optional content to be returned after the rendered template.
116
130
  #
117
131
  # @return [String]
@@ -413,6 +427,22 @@ module ViewComponent
413
427
  # `compile` defines
414
428
  compile
415
429
 
430
+ # Give the child its own personal #render_template_for to protect against the case when
431
+ # eager loading is disabled and the parent component is rendered before the child. In
432
+ # such a scenario, the parent will override ViewComponent::Base#render_template_for,
433
+ # meaning it will not be called for any children and thus not compile their templates.
434
+ if !child.instance_methods(false).include?(:render_template_for) && !child.compiled?
435
+ child.class_eval <<~RUBY, __FILE__, __LINE__ + 1
436
+ def render_template_for(variant = nil)
437
+ # Force compilation here so the compiler always redefines render_template_for.
438
+ # This is mostly a safeguard to prevent infinite recursion.
439
+ self.class.compile(raise_errors: true, force: true)
440
+ # .compile replaces this method; call the new one
441
+ render_template_for(variant)
442
+ end
443
+ RUBY
444
+ end
445
+
416
446
  # If Rails application is loaded, add application url_helpers to the component context
417
447
  # we need to check this to use this gem as a dependency
418
448
  if defined?(Rails) && Rails.application
@@ -445,8 +475,8 @@ module ViewComponent
445
475
  # Do as much work as possible in this step, as doing so reduces the amount
446
476
  # of work done each time a component is rendered.
447
477
  # @private
448
- def compile(raise_errors: false)
449
- compiler.compile(raise_errors: raise_errors)
478
+ def compile(raise_errors: false, force: false)
479
+ compiler.compile(raise_errors: raise_errors, force: force)
450
480
  end
451
481
 
452
482
  # @private
@@ -20,10 +20,11 @@ module ViewComponent
20
20
 
21
21
  def invalidate_class!(klass)
22
22
  cache.delete(klass)
23
+ klass.compiler.reset_render_template_for
23
24
  end
24
25
 
25
26
  def invalidate!
26
- cache.clear
27
+ cache.each { |klass| invalidate_class!(klass) }
27
28
  end
28
29
  end
29
30
  end
@@ -27,12 +27,11 @@ module ViewComponent
27
27
  self.class.mode == DEVELOPMENT_MODE
28
28
  end
29
29
 
30
- def compile(raise_errors: false)
31
- return if compiled?
30
+ def compile(raise_errors: false, force: false)
31
+ return if compiled? && !force
32
+ return if component_class == ViewComponent::Base
32
33
 
33
34
  with_lock do
34
- CompileCache.invalidate_class!(component_class)
35
-
36
35
  subclass_instance_methods = component_class.instance_methods(false)
37
36
 
38
37
  if subclass_instance_methods.include?(:with_content) && raise_errors
@@ -65,13 +64,12 @@ module ViewComponent
65
64
  # as Ruby warns when redefining a method.
66
65
  method_name = call_method_name(template[:variant])
67
66
 
68
- if component_class.instance_methods.include?(method_name.to_sym)
69
- component_class.send(:undef_method, method_name.to_sym)
67
+ if component_class.instance_methods(false).include?(method_name.to_sym)
68
+ component_class.send(:remove_method, method_name.to_sym)
70
69
  end
71
70
 
72
- component_class.class_eval <<-RUBY, template[:path], -1
71
+ component_class.class_eval <<-RUBY, template[:path], 0
73
72
  def #{method_name}
74
- @output_buffer = ActionView::OutputBuffer.new
75
73
  #{compiled_template(template[:path])}
76
74
  end
77
75
  RUBY
@@ -93,14 +91,18 @@ module ViewComponent
93
91
  end
94
92
  end
95
93
 
94
+ def reset_render_template_for
95
+ if component_class.instance_methods(false).include?(:render_template_for)
96
+ component_class.send(:remove_method, :render_template_for)
97
+ end
98
+ end
99
+
96
100
  private
97
101
 
98
102
  attr_reader :component_class
99
103
 
100
104
  def define_render_template_for
101
- if component_class.instance_methods.include?(:render_template_for)
102
- component_class.send(:undef_method, :render_template_for)
103
- end
105
+ reset_render_template_for
104
106
 
105
107
  variant_elsifs = variants.compact.uniq.map do |variant|
106
108
  "elsif variant.to_sym == :#{variant}\n #{call_method_name(variant)}"
@@ -20,6 +20,7 @@ module ViewComponent
20
20
  options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
21
21
  options.preview_route ||= ViewComponent::Base.preview_route
22
22
  options.preview_controller ||= ViewComponent::Base.preview_controller
23
+ options.use_global_output_buffer = false if options.use_global_output_buffer.nil?
23
24
 
24
25
  if options.show_previews
25
26
  options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?(
@@ -57,6 +58,21 @@ module ViewComponent
57
58
  end
58
59
  end
59
60
 
61
+ initializer "view_component.enable_global_output_buffer" do |app|
62
+ ActiveSupport.on_load(:view_component) do
63
+ env_use_gob = ENV.fetch("VIEW_COMPONENT_USE_GLOBAL_OUTPUT_BUFFER", "false") == "true"
64
+ config_use_gob = app.config.view_component.use_global_output_buffer
65
+
66
+ if config_use_gob || env_use_gob
67
+ # :nocov:
68
+ app.config.view_component.use_global_output_buffer = true
69
+ ViewComponent::Base.prepend(ViewComponent::GlobalOutputBuffer)
70
+ ActionView::Base.prepend(ViewComponent::GlobalOutputBuffer::ActionViewMods)
71
+ # :nocov:
72
+ end
73
+ end
74
+ end
75
+
60
76
  initializer "view_component.set_autoload_paths" do |app|
61
77
  options = app.config.view_component
62
78
 
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module GlobalOutputBuffer
5
+ def render_in(view_context, &block)
6
+ unless view_context.output_buffer.is_a?(OutputBufferStack)
7
+ # use instance_variable_set here to avoid triggering the code in the #output_buffer= method below
8
+ view_context.instance_variable_set(:@output_buffer, OutputBufferStack.new(view_context.output_buffer))
9
+ end
10
+
11
+ @output_buffer = view_context.output_buffer
12
+ @global_buffer_in_use = true
13
+
14
+ super(view_context, &block)
15
+ end
16
+
17
+ def perform_render
18
+ # HAML unhelpfully assigns to @output_buffer directly, so we hold onto a reference to
19
+ # it and restore @output_buffer when the HAML engine is finished. In non-HAML cases,
20
+ # @output_buffer and orig_buf will point to the same object, making the reassignment
21
+ # statements no-ops.
22
+ orig_buf = @output_buffer
23
+ @output_buffer.push
24
+ result = render_template_for(@__vc_variant).to_s + _output_postamble
25
+ @output_buffer = orig_buf
26
+ @output_buffer.pop
27
+ result
28
+ end
29
+
30
+ def output_buffer=(other_buffer)
31
+ @output_buffer.replace(other_buffer)
32
+ end
33
+
34
+ def with_output_buffer(buf = nil)
35
+ unless buf
36
+ buf = ActionView::OutputBuffer.new
37
+ if output_buffer && output_buffer.respond_to?(:encoding)
38
+ buf.force_encoding(output_buffer.encoding)
39
+ end
40
+ end
41
+
42
+ output_buffer.push(buf)
43
+ result = nil
44
+
45
+ begin
46
+ yield
47
+ ensure
48
+ # assign result here to avoid a return statement, which will
49
+ # immediately return to the caller and swallow any errors
50
+ result = output_buffer.pop
51
+ end
52
+
53
+ result
54
+ end
55
+
56
+ module ActionViewMods
57
+ def output_buffer=(other_buffer)
58
+ if @output_buffer.is_a?(OutputBufferStack)
59
+ @output_buffer.replace(other_buffer)
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ def with_output_buffer(buf = nil)
66
+ unless buf
67
+ buf = ActionView::OutputBuffer.new
68
+ if @output_buffer && @output_buffer.respond_to?(:encoding)
69
+ buf.force_encoding(@output_buffer.encoding)
70
+ end
71
+ end
72
+
73
+ result = nil
74
+
75
+ if @output_buffer.is_a?(OutputBufferStack)
76
+ @output_buffer.push(buf)
77
+
78
+ begin
79
+ yield
80
+ ensure
81
+ result = @output_buffer.pop
82
+ end
83
+
84
+ result
85
+ else
86
+ @output_buffer, old_buffer = buf, output_buffer
87
+
88
+ begin
89
+ yield
90
+ ensure
91
+ @output_buffer = old_buffer
92
+ end
93
+
94
+ buf
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ class OutputBufferStack
5
+ delegate_missing_to :@current_buffer
6
+ delegate :presence, :present?, :html_safe?, to: :@current_buffer
7
+
8
+ attr_reader :buffer_stack
9
+
10
+ def self.make_frame(*args)
11
+ ActionView::OutputBuffer.new(*args)
12
+ end
13
+
14
+ def initialize(initial_buffer = nil)
15
+ if initial_buffer.is_a?(self.class)
16
+ @current_buffer = self.class.make_frame(initial_buffer.current)
17
+ @buffer_stack = [*initial_buffer.buffer_stack[0..-2], @current_buffer]
18
+ else
19
+ @current_buffer = initial_buffer || self.class.make_frame
20
+ @buffer_stack = [@current_buffer]
21
+ end
22
+ end
23
+
24
+ def replace(buffer)
25
+ return if self == buffer
26
+
27
+ @current_buffer = buffer.current
28
+ @buffer_stack = buffer.buffer_stack
29
+ end
30
+
31
+ def append=(arg)
32
+ @current_buffer.append = arg
33
+ end
34
+
35
+ def safe_append=(arg)
36
+ @current_buffer.safe_append = arg
37
+ end
38
+
39
+ def safe_concat(arg)
40
+ # rubocop:disable Rails/OutputSafety
41
+ @current_buffer.safe_concat(arg)
42
+ # rubocop:enable Rails/OutputSafety
43
+ end
44
+
45
+ def length
46
+ @current_buffer.length
47
+ end
48
+
49
+ def push(buffer = nil)
50
+ buffer ||= self.class.make_frame
51
+ @buffer_stack.push(buffer)
52
+ @current_buffer = buffer
53
+ end
54
+
55
+ def pop
56
+ @buffer_stack.pop.tap do
57
+ @current_buffer = @buffer_stack.last
58
+ end
59
+ end
60
+
61
+ def to_s
62
+ @current_buffer
63
+ end
64
+
65
+ alias_method :current, :to_s
66
+ end
67
+ end
@@ -45,18 +45,12 @@ module ViewComponent
45
45
  if defined?(@__vc_content_set_by_with_content)
46
46
  @__vc_component_instance.with_content(@__vc_content_set_by_with_content)
47
47
 
48
- view_context.capture do
49
- @__vc_component_instance.render_in(view_context)
50
- end
48
+ @__vc_component_instance.render_in(view_context)
51
49
  elsif defined?(@__vc_content_block)
52
- view_context.capture do
53
- # render_in is faster than `parent.render`
54
- @__vc_component_instance.render_in(view_context, &@__vc_content_block)
55
- end
50
+ # render_in is faster than `parent.render`
51
+ @__vc_component_instance.render_in(view_context, &@__vc_content_block)
56
52
  else
57
- view_context.capture do
58
- @__vc_component_instance.render_in(view_context)
59
- end
53
+ @__vc_component_instance.render_in(view_context)
60
54
  end
61
55
  elsif defined?(@__vc_content)
62
56
  @__vc_content
@@ -3,7 +3,6 @@
3
3
  require "erb"
4
4
  require "set"
5
5
  require "i18n"
6
- require "action_view/helpers/translation_helper"
7
6
  require "active_support/concern"
8
7
 
9
8
  module ViewComponent
@@ -68,7 +67,10 @@ module ViewComponent
68
67
  return key.map { |k| translate(k, **options) } if key.is_a?(Array)
69
68
 
70
69
  locale = options.delete(:locale) || ::I18n.locale
70
+ scope = options.delete(:scope)
71
+ scope = scope.join(".") if scope.is_a? Array
71
72
  key = key&.to_s unless key.is_a?(String)
73
+ key = "#{scope}.#{key}" if scope
72
74
  key = "#{i18n_scope}#{key}" if key.start_with?(".")
73
75
 
74
76
  if HTML_SAFE_TRANSLATION_KEY.match?(key)
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 50
6
+ MINOR = 53
7
7
  PATCH = 0
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -11,7 +11,9 @@ module ViewComponent
11
11
  autoload :CompileCache
12
12
  autoload :ComponentError
13
13
  autoload :Deprecation
14
+ autoload :GlobalOutputBuffer
14
15
  autoload :Instrumentation
16
+ autoload :OutputBufferStack
15
17
  autoload :Preview
16
18
  autoload :PreviewTemplateError
17
19
  autoload :TestHelpers
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.50.0
4
+ version: 2.53.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-10 00:00:00.000000000 Z
11
+ date: 2022-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -333,7 +333,9 @@ files:
333
333
  - lib/view_component/docs_builder_component.html.erb
334
334
  - lib/view_component/docs_builder_component.rb
335
335
  - lib/view_component/engine.rb
336
+ - lib/view_component/global_output_buffer.rb
336
337
  - lib/view_component/instrumentation.rb
338
+ - lib/view_component/output_buffer_stack.rb
337
339
  - lib/view_component/polymorphic_slots.rb
338
340
  - lib/view_component/preview.rb
339
341
  - lib/view_component/preview_template_error.rb