view_component 2.50.0 → 2.53.0

Sign up to get free protection for your applications and to get access to all the features.

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