view_component 2.49.1 → 3.23.2

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/app/assets/vendor/prism.css +3 -195
  4. data/app/assets/vendor/prism.min.js +11 -11
  5. data/app/controllers/concerns/view_component/preview_actions.rb +108 -0
  6. data/app/controllers/view_components_controller.rb +1 -87
  7. data/app/controllers/view_components_system_test_controller.rb +30 -0
  8. data/app/helpers/preview_helper.rb +30 -12
  9. data/app/views/view_components/_preview_source.html.erb +3 -3
  10. data/app/views/view_components/preview.html.erb +2 -2
  11. data/docs/CHANGELOG.md +1653 -24
  12. data/lib/rails/generators/abstract_generator.rb +16 -10
  13. data/lib/rails/generators/component/component_generator.rb +8 -4
  14. data/lib/rails/generators/component/templates/component.rb.tt +3 -2
  15. data/lib/rails/generators/erb/component_generator.rb +1 -1
  16. data/lib/rails/generators/locale/component_generator.rb +4 -4
  17. data/lib/rails/generators/preview/component_generator.rb +17 -3
  18. data/lib/rails/generators/preview/templates/component_preview.rb.tt +5 -1
  19. data/lib/rails/generators/rspec/component_generator.rb +15 -3
  20. data/lib/rails/generators/rspec/templates/component_spec.rb.tt +3 -1
  21. data/lib/rails/generators/stimulus/component_generator.rb +8 -3
  22. data/lib/rails/generators/stimulus/templates/component_controller.ts.tt +9 -0
  23. data/lib/rails/generators/test_unit/templates/component_test.rb.tt +3 -1
  24. data/lib/view_component/base.rb +352 -196
  25. data/lib/view_component/capture_compatibility.rb +44 -0
  26. data/lib/view_component/collection.rb +28 -9
  27. data/lib/view_component/compiler.rb +162 -193
  28. data/lib/view_component/config.rb +225 -0
  29. data/lib/view_component/configurable.rb +17 -0
  30. data/lib/view_component/deprecation.rb +8 -0
  31. data/lib/view_component/engine.rb +74 -47
  32. data/lib/view_component/errors.rb +240 -0
  33. data/lib/view_component/inline_template.rb +55 -0
  34. data/lib/view_component/instrumentation.rb +10 -2
  35. data/lib/view_component/preview.rb +21 -19
  36. data/lib/view_component/rails/tasks/view_component.rake +11 -2
  37. data/lib/view_component/render_component_helper.rb +1 -0
  38. data/lib/view_component/render_component_to_string_helper.rb +1 -1
  39. data/lib/view_component/render_to_string_monkey_patch.rb +1 -1
  40. data/lib/view_component/rendering_component_helper.rb +1 -1
  41. data/lib/view_component/rendering_monkey_patch.rb +1 -1
  42. data/lib/view_component/slot.rb +119 -1
  43. data/lib/view_component/slotable.rb +393 -96
  44. data/lib/view_component/slotable_default.rb +20 -0
  45. data/lib/view_component/system_test_case.rb +13 -0
  46. data/lib/view_component/system_test_helpers.rb +27 -0
  47. data/lib/view_component/template.rb +134 -0
  48. data/lib/view_component/test_helpers.rb +208 -47
  49. data/lib/view_component/translatable.rb +51 -33
  50. data/lib/view_component/use_helpers.rb +42 -0
  51. data/lib/view_component/version.rb +5 -4
  52. data/lib/view_component/with_content_helper.rb +3 -8
  53. data/lib/view_component.rb +7 -12
  54. metadata +339 -57
  55. data/lib/rails/generators/component/USAGE +0 -13
  56. data/lib/view_component/content_areas.rb +0 -57
  57. data/lib/view_component/polymorphic_slots.rb +0 -73
  58. data/lib/view_component/preview_template_error.rb +0 -6
  59. data/lib/view_component/previewable.rb +0 -62
  60. data/lib/view_component/slot_v2.rb +0 -104
  61. data/lib/view_component/slotable_v2.rb +0 -307
  62. data/lib/view_component/template_error.rb +0 -9
  63. data/lib/yard/mattr_accessor_handler.rb +0 -19
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "view_component/deprecation"
4
+
5
+ module ViewComponent
6
+ class Config
7
+ class << self
8
+ # `new` without any arguments initializes the default configuration, but
9
+ # it's important to differentiate in case that's no longer the case in
10
+ # future.
11
+ alias_method :default, :new
12
+
13
+ def defaults
14
+ ActiveSupport::OrderedOptions.new.merge!({
15
+ generate: default_generate_options,
16
+ preview_controller: "ViewComponentsController",
17
+ preview_route: "/rails/view_components",
18
+ show_previews_source: false,
19
+ instrumentation_enabled: false,
20
+ use_deprecated_instrumentation_name: true,
21
+ render_monkey_patch_enabled: true,
22
+ view_component_path: "app/components",
23
+ component_parent_class: nil,
24
+ show_previews: Rails.env.development? || Rails.env.test?,
25
+ preview_paths: default_preview_paths,
26
+ test_controller: "ApplicationController",
27
+ default_preview_layout: nil,
28
+ capture_compatibility_patch_enabled: false
29
+ })
30
+ end
31
+
32
+ # @!attribute generate
33
+ # @return [ActiveSupport::OrderedOptions]
34
+ # The subset of configuration options relating to generators.
35
+ #
36
+ # All options under this namespace default to `false` unless otherwise
37
+ # stated.
38
+ #
39
+ # #### `#sidecar`
40
+ #
41
+ # Always generate a component with a sidecar directory:
42
+ #
43
+ # config.view_component.generate.sidecar = true
44
+ #
45
+ # #### `#stimulus_controller`
46
+ #
47
+ # Always generate a Stimulus controller alongside the component:
48
+ #
49
+ # config.view_component.generate.stimulus_controller = true
50
+ #
51
+ # #### `#typescript`
52
+ #
53
+ # Generate TypeScript files instead of JavaScript files:
54
+ #
55
+ # config.view_component.generate.typescript = true
56
+ #
57
+ # #### `#locale`
58
+ #
59
+ # Always generate translations file alongside the component:
60
+ #
61
+ # config.view_component.generate.locale = true
62
+ #
63
+ # #### `#distinct_locale_files`
64
+ #
65
+ # Always generate as many translations files as available locales:
66
+ #
67
+ # config.view_component.generate.distinct_locale_files = true
68
+ #
69
+ # One file will be generated for each configured `I18n.available_locales`,
70
+ # falling back to `[:en]` when no `available_locales` is defined.
71
+ #
72
+ # #### `#preview`
73
+ #
74
+ # Always generate a preview alongside the component:
75
+ #
76
+ # config.view_component.generate.preview = true
77
+ #
78
+ # #### #preview_path
79
+ #
80
+ # Path to generate preview:
81
+ #
82
+ # config.view_component.generate.preview_path = "test/components/previews"
83
+ #
84
+ # Required when there is more than one path defined in preview_paths.
85
+ # Defaults to `""`. If this is blank, the generator will use
86
+ # `ViewComponent.config.preview_paths` if defined,
87
+ # `"test/components/previews"` otherwise
88
+ #
89
+ # #### `#use_component_path_for_rspec_tests`
90
+ #
91
+ # Whether to use the `config.view_component_path` when generating new
92
+ # RSpec component tests:
93
+ #
94
+ # config.view_component.generate.use_component_path_for_rspec_tests = true
95
+ #
96
+ # When set to `true`, the generator will use the `view_component_path` to
97
+ # decide where to generate the new RSpec component test.
98
+ # For example, if the `view_component_path` is
99
+ # `app/views/components`, then the generator will create a new spec file
100
+ # in `spec/views/components/` rather than the default `spec/components/`.
101
+
102
+ # @!attribute preview_controller
103
+ # @return [String]
104
+ # The controller used for previewing components.
105
+ # Defaults to `ViewComponentsController`.
106
+
107
+ # @!attribute preview_route
108
+ # @return [String]
109
+ # The entry route for component previews.
110
+ # Defaults to `"/rails/view_components"`.
111
+
112
+ # @!attribute show_previews_source
113
+ # @return [Boolean]
114
+ # Whether to display source code previews in component previews.
115
+ # Defaults to `false`.
116
+
117
+ # @!attribute instrumentation_enabled
118
+ # @return [Boolean]
119
+ # Whether ActiveSupport notifications are enabled.
120
+ # Defaults to `false`.
121
+
122
+ # @!attribute use_deprecated_instrumentation_name
123
+ # @return [Boolean]
124
+ # Whether ActiveSupport Notifications use the private name `"!render.view_component"`
125
+ # or are made more publicly available via `"render.view_component"`.
126
+ # Will be removed in next major version.
127
+ # Defaults to `true`.
128
+
129
+ # @!attribute render_monkey_patch_enabled
130
+ # @return [Boolean] Whether the #render method should be monkey patched.
131
+ # If this is disabled, use `#render_component` or
132
+ # `#render_component_to_string` instead.
133
+ # Defaults to `true`.
134
+
135
+ # @!attribute view_component_path
136
+ # @return [String]
137
+ # The path in which components, their templates, and their sidecars should
138
+ # be stored.
139
+ # Defaults to `"app/components"`.
140
+
141
+ # @!attribute component_parent_class
142
+ # @return [String]
143
+ # The parent class from which generated components will inherit.
144
+ # Defaults to `nil`. If this is falsy, generators will use
145
+ # `"ApplicationComponent"` if defined, `"ViewComponent::Base"` otherwise.
146
+
147
+ # @!attribute show_previews
148
+ # @return [Boolean]
149
+ # Whether component previews are enabled.
150
+ # Defaults to `true` in development and test environments.
151
+
152
+ # @!attribute preview_paths
153
+ # @return [Array<String>]
154
+ # The locations in which component previews will be looked up.
155
+ # Defaults to `['test/components/previews']` relative to your Rails root.
156
+
157
+ # @!attribute test_controller
158
+ # @return [String]
159
+ # The controller used for testing components.
160
+ # Can also be configured on a per-test basis using `#with_controller_class`.
161
+ # Defaults to `ApplicationController`.
162
+
163
+ # @!attribute default_preview_layout
164
+ # @return [String]
165
+ # A custom default layout used for the previews index page and individual
166
+ # previews.
167
+ # Defaults to `nil`. If this is falsy, `"component_preview"` is used.
168
+
169
+ # @!attribute capture_compatibility_patch_enabled
170
+ # @return [Boolean]
171
+ # Enables the experimental capture compatibility patch that makes ViewComponent
172
+ # compatible with forms, capture, and other built-ins.
173
+ # previews.
174
+ # Defaults to `false`.
175
+
176
+ def default_preview_paths
177
+ (default_rails_preview_paths + default_rails_engines_preview_paths).uniq
178
+ end
179
+
180
+ def default_rails_preview_paths
181
+ return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews")
182
+
183
+ ["#{Rails.root}/test/components/previews"]
184
+ end
185
+
186
+ def default_rails_engines_preview_paths
187
+ return [] unless defined?(Rails::Engine)
188
+
189
+ registered_rails_engines_with_previews.map do |descendant|
190
+ "#{descendant.root}/test/components/previews"
191
+ end
192
+ end
193
+
194
+ def registered_rails_engines_with_previews
195
+ Rails::Engine.descendants.select do |descendant|
196
+ defined?(descendant.root) && Dir.exist?("#{descendant.root}/test/components/previews")
197
+ end
198
+ end
199
+
200
+ def default_generate_options
201
+ options = ActiveSupport::OrderedOptions.new(false)
202
+ options.preview_path = ""
203
+ options
204
+ end
205
+ end
206
+
207
+ # @!attribute current
208
+ # @return [ViewComponent::Config]
209
+ # Returns the current ViewComponent::Config. This is persisted against this
210
+ # class so that config options remain accessible before the rest of
211
+ # ViewComponent has loaded. Defaults to an instance of ViewComponent::Config
212
+ # with all other documented defaults set.
213
+ class_attribute :current, default: defaults, instance_predicate: false
214
+
215
+ def initialize
216
+ @config = self.class.defaults
217
+ end
218
+
219
+ delegate_missing_to :config
220
+
221
+ private
222
+
223
+ attr_reader :config
224
+ end
225
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Configurable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ next if respond_to?(:config) && config.respond_to?(:view_component) && config.respond_to_missing?(:test_controller)
9
+
10
+ include ActiveSupport::Configurable
11
+
12
+ configure do |config|
13
+ config.view_component ||= ActiveSupport::InheritableOptions.new
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/deprecation"
4
+
5
+ module ViewComponent
6
+ DEPRECATION_HORIZON = "4.0.0"
7
+ Deprecation = ActiveSupport::Deprecation.new(DEPRECATION_HORIZON, "ViewComponent")
8
+ end
@@ -1,38 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails"
4
+ require "view_component/config"
5
+ require "view_component/deprecation"
4
6
 
5
7
  module ViewComponent
6
8
  class Engine < Rails::Engine # :nodoc:
7
- config.view_component = ActiveSupport::OrderedOptions.new
8
- config.view_component.preview_paths ||= []
9
+ config.view_component = ViewComponent::Config.current
9
10
 
10
- rake_tasks do
11
- load "view_component/rails/tasks/view_component.rake"
11
+ if Rails.version.to_f < 8.0
12
+ rake_tasks do
13
+ load "view_component/rails/tasks/view_component.rake"
14
+ end
15
+ else
16
+ initializer "view_component.stats_directories" do |app|
17
+ require "rails/code_statistics"
18
+
19
+ if Rails.root.join(ViewComponent::Base.view_component_path).directory?
20
+ Rails::CodeStatistics.register_directory("ViewComponents", ViewComponent::Base.view_component_path)
21
+ end
22
+
23
+ if Rails.root.join("test/components").directory?
24
+ Rails::CodeStatistics.register_directory("ViewComponent tests", "test/components", test_directory: true)
25
+ end
26
+ end
12
27
  end
13
28
 
14
29
  initializer "view_component.set_configs" do |app|
15
30
  options = app.config.view_component
16
31
 
17
- options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil?
18
- options.show_previews = Rails.env.development? || Rails.env.test? if options.show_previews.nil?
19
- options.show_previews_source ||= ViewComponent::Base.show_previews_source
32
+ %i[generate preview_controller preview_route show_previews_source].each do |config_option|
33
+ options[config_option] ||= ViewComponent::Base.public_send(config_option)
34
+ end
20
35
  options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
21
- options.preview_route ||= ViewComponent::Base.preview_route
22
- options.preview_controller ||= ViewComponent::Base.preview_controller
36
+ options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil?
37
+ options.show_previews = (Rails.env.development? || Rails.env.test?) if options.show_previews.nil?
23
38
 
24
39
  if options.show_previews
40
+ # This is still necessary because when `config.view_component` is declared, `Rails.root` is unspecified.
25
41
  options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?(
26
42
  "#{Rails.root}/test/components/previews"
27
43
  )
28
44
 
29
- if options.preview_path.present?
30
- ActiveSupport::Deprecation.warn(
31
- "`preview_path` will be removed in v3.0.0. Use `preview_paths` instead."
32
- )
33
- options.preview_paths << options.preview_path
34
- end
35
-
36
45
  if options.show_previews_source
37
46
  require "method_source"
38
47
 
@@ -41,27 +50,38 @@ module ViewComponent
41
50
  end
42
51
  end
43
52
  end
44
-
45
- ActiveSupport.on_load(:view_component) do
46
- options.each { |k, v| send("#{k}=", v) if respond_to?("#{k}=") }
47
- end
48
53
  end
49
54
 
50
55
  initializer "view_component.enable_instrumentation" do |app|
51
56
  ActiveSupport.on_load(:view_component) do
52
57
  if app.config.view_component.instrumentation_enabled.present?
53
- # :nocov:
58
+ # :nocov: Re-executing the below in tests duplicates initializers and causes order-dependent failures.
54
59
  ViewComponent::Base.prepend(ViewComponent::Instrumentation)
60
+ if app.config.view_component.use_deprecated_instrumentation_name
61
+ ViewComponent::Deprecation.deprecation_warning(
62
+ "!render.view_component",
63
+ "Use the new instrumentation key `render.view_component` instead. See https://viewcomponent.org/guide/instrumentation.html"
64
+ )
65
+ end
55
66
  # :nocov:
56
67
  end
57
68
  end
58
69
  end
59
70
 
71
+ # :nocov:
72
+ initializer "view_component.enable_capture_patch" do |app|
73
+ ActiveSupport.on_load(:view_component) do
74
+ ActionView::Base.include(ViewComponent::CaptureCompatibility) if app.config.view_component.capture_compatibility_patch_enabled
75
+ end
76
+ end
77
+ # :nocov:
78
+
60
79
  initializer "view_component.set_autoload_paths" do |app|
61
80
  options = app.config.view_component
62
81
 
63
82
  if options.show_previews && !options.preview_paths.empty?
64
- ActiveSupport::Dependencies.autoload_paths.concat(options.preview_paths)
83
+ paths_to_add = options.preview_paths - ActiveSupport::Dependencies.autoload_paths
84
+ ActiveSupport::Dependencies.autoload_paths.concat(paths_to_add) if paths_to_add.any?
65
85
  end
66
86
  end
67
87
 
@@ -71,15 +91,12 @@ module ViewComponent
71
91
  end
72
92
  end
73
93
 
74
- initializer "view_component.compile_config_methods" do
75
- ActiveSupport.on_load(:view_component) do
76
- config.compile_methods! if config.respond_to?(:compile_methods!)
77
- end
78
- end
79
-
80
94
  initializer "view_component.monkey_patch_render" do |app|
81
95
  next if Rails.version.to_f >= 6.1 || !app.config.view_component.render_monkey_patch_enabled
82
96
 
97
+ # :nocov:
98
+ ViewComponent::Deprecation.deprecation_warning("Monkey patching `render`", "ViewComponent 4.0 will remove the `render` monkey patch")
99
+
83
100
  ActiveSupport.on_load(:action_view) do
84
101
  require "view_component/render_monkey_patch"
85
102
  ActionView::Base.prepend ViewComponent::RenderMonkeyPatch
@@ -91,11 +108,15 @@ module ViewComponent
91
108
  ActionController::Base.prepend ViewComponent::RenderingMonkeyPatch
92
109
  ActionController::Base.prepend ViewComponent::RenderToStringMonkeyPatch
93
110
  end
111
+ # :nocov:
94
112
  end
95
113
 
96
- initializer "view_component.include_render_component" do |app|
114
+ initializer "view_component.include_render_component" do |_app|
97
115
  next if Rails.version.to_f >= 6.1
98
116
 
117
+ # :nocov:
118
+ ViewComponent::Deprecation.deprecation_warning("using `render_component`", "ViewComponent 4.0 will remove `render_component`")
119
+
99
120
  ActiveSupport.on_load(:action_view) do
100
121
  require "view_component/render_component_helper"
101
122
  ActionView::Base.include ViewComponent::RenderComponentHelper
@@ -107,20 +128,21 @@ module ViewComponent
107
128
  ActionController::Base.include ViewComponent::RenderingComponentHelper
108
129
  ActionController::Base.include ViewComponent::RenderComponentToStringHelper
109
130
  end
131
+ # :nocov:
110
132
  end
111
133
 
112
134
  initializer "static assets" do |app|
113
- if app.config.view_component.show_previews
135
+ if serve_static_preview_assets?(app.config)
114
136
  app.middleware.use(::ActionDispatch::Static, "#{root}/app/assets/vendor")
115
137
  end
116
138
  end
117
139
 
118
- initializer "compiler mode" do |app|
119
- ViewComponent::Compiler.mode = if Rails.env.development? || Rails.env.test?
120
- ViewComponent::Compiler::DEVELOPMENT_MODE
121
- else
122
- ViewComponent::Compiler::PRODUCTION_MODE
123
- end
140
+ def serve_static_preview_assets?(app_config)
141
+ app_config.view_component.show_previews && app_config.public_file_server.enabled
142
+ end
143
+
144
+ initializer "compiler mode" do |_app|
145
+ ViewComponent::Compiler.development_mode = (Rails.env.development? || Rails.env.test?)
124
146
  end
125
147
 
126
148
  config.after_initialize do |app|
@@ -146,20 +168,25 @@ module ViewComponent
146
168
  end
147
169
  end
148
170
 
171
+ if Rails.env.test?
172
+ app.routes.prepend do
173
+ get("_system_test_entrypoint", to: "view_components_system_test#system_test_entrypoint")
174
+ end
175
+ end
176
+
177
+ # :nocov:
178
+ if RUBY_VERSION < "3.2.0"
179
+ ViewComponent::Deprecation.deprecation_warning("Support for Ruby versions < 3.2.0", "ViewComponent v4 will remove support for Ruby versions < 3.2.0 no earlier than April 1, 2025")
180
+ end
181
+
182
+ if Rails.version.to_f < 7.1
183
+ ViewComponent::Deprecation.deprecation_warning("Support for Rails versions < 7.1", "ViewComponent v4 will remove support for Rails versions < 7.1 no earlier than April 1, 2025")
184
+ end
185
+ # :nocov:
186
+
149
187
  app.executor.to_run :before do
150
188
  CompileCache.invalidate! unless ActionView::Base.cache_template_loading
151
189
  end
152
190
  end
153
191
  end
154
192
  end
155
-
156
- # :nocov:
157
- unless defined?(ViewComponent::Base)
158
- ActiveSupport::Deprecation.warn(
159
- "This manually engine loading is deprecated and will be removed in v3.0.0. " \
160
- "Remove `require \"view_component/engine\"`."
161
- )
162
-
163
- require "view_component"
164
- end
165
- # :nocov: