Class: Ruact::ErbPreprocessor
- Inherits:
-
Object
- Object
- Ruact::ErbPreprocessor
- Defined in:
- lib/ruact/erb_preprocessor.rb
Overview
Transforms ERB source before Ruby evaluation.
It handles one thing: PascalCase component tags with {expr} props.
<LikeButton postId={@post.id} initialCount={5} />
becomes a placeholder that evaluates the props as Ruby:
<%= __rsc_component__("LikeButton", { "postId" => @post.id, "initialCount" => 5 }) %>
The placeholder is replaced by an HTML comment with a unique token:
<!-- __RSC_COMPONENT_0__ -->
The actual ClientReference + props are registered in the binding and collected by HtmlConverter after the ERB renders.
Constant Summary collapse
- COMPONENT_TAG_RE =
Matches a PascalCase opening tag with optional attributes and optional self-closing. Examples:
<Button /> <LikeButton postId={@post.id} initialCount={5} /> <Dialog open={true}> %r{<([A-Z][A-Za-z0-9]*)(\s[^>]*)?\s*/?>}- SUSPENSE_OPEN_RE =
Matches <Suspense …> opening tags (handled before general PascalCase processing).
/<Suspense\b([^>]*?)>/m- SUSPENSE_CLOSE_RE =
%r{</Suspense>}- PROP_RE =
Matches a {ruby_expr} attribute value — captures everything between the braces. We use a simple bracket-depth counter approach during scanning instead of regex because expressions can contain nested braces: {foo.bar({ a: 1 })}.
/\b([a-zA-Z_][a-zA-Z0-9_]*)=\{/
Class Method Summary collapse
-
.transform(source) ⇒ Object
Transform ERB source, replacing component tags with ERB placeholders.
Instance Method Summary collapse
Class Method Details
.transform(source) ⇒ Object
Transform ERB source, replacing component tags with ERB placeholders. Returns the transformed source string.
38 39 40 |
# File 'lib/ruact/erb_preprocessor.rb', line 38 def self.transform(source) new.transform(source) end |
Instance Method Details
#transform(source) ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/ruact/erb_preprocessor.rb', line 42 def transform(source) # Step 1: transform <Suspense> paired tags into <rsc-suspense> HTML elements. # This runs before the general component regex so Suspense isn't treated as a component. result = source .gsub(SUSPENSE_OPEN_RE) do attrs = ::Regexp.last_match(1) fallback = extract_string_attr(attrs, "fallback") || "" escaped = fallback.gsub('"', """) %(<rsc-suspense data-rsc-fallback="#{escaped}">) end .gsub(SUSPENSE_CLOSE_RE, "</rsc-suspense>") # Step 2: transform remaining PascalCase self-closing / opening component tags. result.gsub(COMPONENT_TAG_RE) do |match| component_name = ::Regexp.last_match(1) attrs_string = ::Regexp.last_match(2).to_s.strip match_start = ::Regexp.last_match.begin(0) line = result[0...match_start].count("\n") + 1 begin props_ruby = parse_props(attrs_string) props_hash = props_ruby.empty? ? "{}" : "{ #{props_ruby} }" %(<%= __rsc_component__(#{component_name.inspect}, #{props_hash}) %>) rescue PreprocessorError => e raise PreprocessorError, "#{e.} at line #{line}: #{match.strip}" end end end |