Skip to content

Commit 711b9cd

Browse files
authored
Pre process Slim templates embedded in Ruby files (#17336)
This PR fixes an issue where embedded Slim templates inside of Ruby files are not pre processed because we pre process based on a file extension. This PR also handles embedded SLIM templates using the following syntax: ```rb slim_template <<~SLIM .flex .flex-1 h1 Hello World .flex-1 p This is a test SLIM ``` ~~As far as I can tell, this is only a Slim template thing and not a Haml template thing but I could be wrong here. See: https://viewcomponent.org/guide/templates.html#interpolations~~ The ViewComponent package handles anything that looks like `{lang}_template`, so the lang here will be used as the pre processing language for now. Fixes: #17334 # Test plan 1. Added test for this 2. Existing tests pass 3. Made sure that the snippet from the issue works as expected: Added an example where we have a `slim_template` and a `svelte_template` to prove that it embeds based on the language. I also added a `html_template` with Svelte syntax to really make sure that that _doesn't_ work. <img width="1816" alt="image" src="https://github.com/user-attachments/assets/35564a32-9c46-4b51-bb1f-e02f4ffe8b01" />
1 parent fac8f7d commit 711b9cd

File tree

4 files changed

+98
-0
lines changed

4 files changed

+98
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626

2727
- Vite: Ensure that updates to an imported CSS file are properly propagated after updating templates ([#17347](https://github.com/tailwindlabs/tailwindcss/pull/17347))
2828
- Fix class extraction followed by `(` in Pug ([#17320](https://github.com/tailwindlabs/tailwindcss/pull/17320))
29+
- Pre process `Slim` templates embedded in Ruby files ([#17336](https://github.com/tailwindlabs/tailwindcss/pull/17336))
2930

3031
### [4.0.15] - 2025-03-20
3132

Cargo.lock

+27
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxide/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ bexpand = "1.2.0"
1919
fast-glob = "0.4.3"
2020
classification-macros = { path = "../classification-macros" }
2121
regex = "1.11.1"
22+
fancy-regex = "0.14.0"
2223

2324
[dev-dependencies]
2425
tempfile = "3.13.0"

crates/oxide/src/extractor/pre_processors/ruby.rs

+69
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
use crate::cursor;
44
use crate::extractor::bracket_stack;
55
use crate::extractor::pre_processors::pre_processor::PreProcessor;
6+
use crate::pre_process_input;
7+
use bstr::ByteSlice;
8+
use fancy_regex::Regex;
9+
use std::sync;
10+
11+
static TEMPLATE_REGEX: sync::LazyLock<Regex> = sync::LazyLock::new(|| {
12+
Regex::new(r#"\s*(.*?)_template\s*<<[-~]?([A-Z]+?)\n([\s\S]*?)\2"#).unwrap()
13+
});
614

715
#[derive(Debug, Default)]
816
pub struct Ruby;
@@ -14,6 +22,20 @@ impl PreProcessor for Ruby {
1422
let mut cursor = cursor::Cursor::new(content);
1523
let mut bracket_stack = bracket_stack::BracketStack::default();
1624

25+
// Extract embedded template languages
26+
// https://viewcomponent.org/guide/templates.html#interpolations
27+
let content_as_str = std::str::from_utf8(content).unwrap();
28+
for capture in TEMPLATE_REGEX
29+
.captures_iter(content_as_str)
30+
.filter_map(Result::ok)
31+
{
32+
let lang = capture.get(1).unwrap().as_str();
33+
let body = capture.get(3).unwrap().as_str();
34+
let replaced = pre_process_input(body.as_bytes(), lang);
35+
result = result.replace(body, replaced);
36+
}
37+
38+
// Ruby extraction
1739
while cursor.pos < len {
1840
// Looking for `%w` or `%W`
1941
if cursor.curr != b'%' && !matches!(cursor.next, b'w' | b'W') {
@@ -153,4 +175,51 @@ mod tests {
153175
Ruby::test_extract_contains(input, expected);
154176
}
155177
}
178+
179+
// https://github.com/tailwindlabs/tailwindcss/issues/17334
180+
#[test]
181+
fn test_embedded_slim_extraction() {
182+
let input = r#"
183+
class QweComponent < ApplicationComponent
184+
slim_template <<~SLIM
185+
button.rounded-full.bg-red-500
186+
| Some text
187+
button.rounded-full(
188+
class="flex"
189+
)
190+
| Some text
191+
SLIM
192+
end
193+
"#;
194+
195+
Ruby::test_extract_contains(input, vec!["rounded-full", "bg-red-500", "flex"]);
196+
197+
// Embedded Svelte just to verify that we properly pick up the `{x}_template`
198+
let input = r#"
199+
class QweComponent < ApplicationComponent
200+
svelte_template <<~HTML
201+
<div class:flex="true"></div>
202+
HTML
203+
end
204+
"#;
205+
206+
Ruby::test_extract_contains(input, vec!["flex"]);
207+
208+
// Together in the same file
209+
let input = r#"
210+
class QweComponent < ApplicationComponent
211+
slim_template <<~SLIM
212+
button.z-1.z-2
213+
| Some text
214+
SLIM
215+
end
216+
217+
class QweComponent < ApplicationComponent
218+
svelte_template <<~HTML
219+
<div class:z-3="true"></div>
220+
HTML
221+
end
222+
"#;
223+
Ruby::test_extract_contains(input, vec!["z-1", "z-2", "z-3"]);
224+
}
156225
}

0 commit comments

Comments
 (0)