Class: JsxRosetta::Backend::ViewComponent
- Defined in:
- lib/jsx_rosetta/backend/view_component.rb,
lib/jsx_rosetta/backend/view_component/expression_translator.rb
Overview
Emits a Rails ViewComponent (one Ruby class + one ERB template) from an IR::Component.
Phase 3 scope:
- Single component per emit.
- JSX prop names lowered to snake_case Ruby kwargs and matching
`@instance_variable` assignments.
- JS expressions translated via ExpressionTranslator where the
shape is recognized; otherwise emitted as a TODO marker plus
verbatim source.
- HTML attributes emitted directly; className / template-literal
class expressions inlined into the `class="..."` attribute.
Phase 4a additions:
- `children` prop is treated as ViewComponent's default content
slot: it's filtered out of the initializer and rendered as
`<%= content %>` wherever the IR has IR::Slot(name: "children").
- IR::Conditional renders as `<% if %>...<% else %>...<% end %>`.
Direct Known Subclasses
Defined Under Namespace
Classes: ExpressionTranslator
Constant Summary collapse
- DEFAULT_SLOT_NAME =
"children"- VOID_ELEMENTS =
%w[area base br col embed hr img input link meta param source track wbr].freeze
- DEFAULT_HELPERS =
JSX component names that have a direct Rails view-helper analog. Override per-instance via ‘ViewComponent.new(helpers: …)`, or disable by passing `helpers: false`.
{ "Link" => { method: :link_to, positional: :href }.freeze, "Image" => { method: :image_tag, positional: :src }.freeze }.freeze
Instance Method Summary collapse
- #emit(component) ⇒ Object
- #erb_path(base_name) ⇒ Object
-
#initialize(helpers: nil, layout: :sidecar) ⇒ ViewComponent
constructor
A new instance of ViewComponent.
- #render_stimulus_controller_js(component) ⇒ Object
- #stimulus_identifier(component) ⇒ Object
- #stimulus_method_lines(method) ⇒ Object
- #stimulus_path(component, base_name) ⇒ Object
Constructor Details
#initialize(helpers: nil, layout: :sidecar) ⇒ ViewComponent
Returns a new instance of ViewComponent.
39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/jsx_rosetta/backend/view_component.rb', line 39 def initialize(helpers: nil, layout: :sidecar) super() @helpers = case helpers when nil then DEFAULT_HELPERS when false then {} else helpers end unless %i[sidecar flat].include?(layout) raise ArgumentError, "unknown layout: #{layout.inspect} (expected :sidecar or :flat)" end @layout = layout end |
Instance Method Details
#emit(component) ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/jsx_rosetta/backend/view_component.rb', line 53 def emit(component) prop_names = component.props.map(&:name) prop_names << component.rest_prop_name if component.rest_prop_name translator = ExpressionTranslator.new(prop_names: prop_names) base_name = "#{AST::Inflector.underscore(component.name)}_component" @stimulus_identifier = component.stimulus_methods.any? ? stimulus_identifier(component) : nil files = [ File.new(path: "#{base_name}.rb", contents: render_ruby_class(component, translator)), File.new(path: erb_path(base_name), contents: render_erb_template(component, translator)) ] if component.stimulus_methods.any? files << File.new( path: stimulus_path(component, base_name), contents: render_stimulus_controller_js(component) ) end files end |
#erb_path(base_name) ⇒ Object
74 75 76 |
# File 'lib/jsx_rosetta/backend/view_component.rb', line 74 def erb_path(base_name) @layout == :sidecar ? "#{base_name}/#{base_name}.html.erb" : "#{base_name}.html.erb" end |
#render_stimulus_controller_js(component) ⇒ Object
87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/jsx_rosetta/backend/view_component.rb', line 87 def render_stimulus_controller_js(component) lines = [ 'import { Controller } from "@hotwired/stimulus";', "", "export default class extends Controller {" ] component.stimulus_methods.each_with_index do |method, idx| lines << "" if idx.positive? lines.concat(stimulus_method_lines(method)) end lines << "}" "#{lines.join("\n")}\n" end |
#stimulus_identifier(component) ⇒ Object
83 84 85 |
# File 'lib/jsx_rosetta/backend/view_component.rb', line 83 def stimulus_identifier(component) AST::Inflector.underscore(component.name).tr("_", "-") end |
#stimulus_method_lines(method) ⇒ Object
101 102 103 104 105 106 107 108 109 |
# File 'lib/jsx_rosetta/backend/view_component.rb', line 101 def stimulus_method_lines(method) body_lines = method.body_source.strip.split("\n") commented = body_lines.map { |line| " // #{line}" } [" // TODO: translate from the original JSX handler:"] + commented + [ " #{method.name}(event) {", " // ...", " }" ] end |
#stimulus_path(component, base_name) ⇒ Object
78 79 80 81 |
# File 'lib/jsx_rosetta/backend/view_component.rb', line 78 def stimulus_path(component, base_name) controller_filename = "#{AST::Inflector.underscore(component.name)}_controller.js" @layout == :sidecar ? "#{base_name}/#{controller_filename}" : controller_filename end |