Skip to content

Commit b3d632b

Browse files
Support inline script (#229)
* Support inline script * Fix lints
1 parent c67a322 commit b3d632b

File tree

3 files changed

+89
-14
lines changed

3 files changed

+89
-14
lines changed

mountaineer/__tests__/test_render.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,34 @@
8888
'<script src="/script4.js" test-attr="test-value"></script>',
8989
],
9090
),
91+
# Inline script tags
92+
(
93+
Metadata(
94+
scripts=[
95+
ScriptAttribute(
96+
inline="console.log('Hello, world!');",
97+
),
98+
ScriptAttribute(
99+
inline="alert('Test');",
100+
asynchronous=True,
101+
),
102+
ScriptAttribute(
103+
inline="window.onload = function() {};",
104+
defer=True,
105+
),
106+
ScriptAttribute(
107+
inline="var x = 1;",
108+
optional_attributes={"type": "text/javascript"},
109+
),
110+
],
111+
),
112+
[
113+
"<script>console.log('Hello, world!');</script>",
114+
"<script async>alert('Test');</script>",
115+
"<script defer>window.onload = function() {};</script>",
116+
'<script type="text/javascript">var x = 1;</script>',
117+
],
118+
),
91119
],
92120
)
93121
def test_build_header(metadata: Metadata, expected_tags: list[str]):
@@ -332,6 +360,18 @@ def test_merge_metadatas(metadatas: list[Metadata], expected_metadata: Metadata)
332360
assert metadata == expected_metadata
333361

334362

363+
def test_script_attribute_requires_src_or_inline():
364+
"""Test that ScriptAttribute requires either src or inline."""
365+
with pytest.raises(ValueError, match="Either 'src' or 'inline' must be provided"):
366+
ScriptAttribute()
367+
368+
369+
def test_script_attribute_cannot_have_both_src_and_inline():
370+
"""Test that ScriptAttribute cannot have both src and inline."""
371+
with pytest.raises(ValueError, match="Cannot specify both 'src' and 'inline'"):
372+
ScriptAttribute(src="/script.js", inline="console.log('test');")
373+
374+
335375
@pytest.mark.parametrize(
336376
"initial_url,new_sha,expected_url",
337377
[

mountaineer/render.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,30 @@ class ScriptAttribute(HashableAttribute, BaseModel):
213213
)
214214
```
215215
216+
Or with inline content:
217+
218+
```python {{ sticky: True }}
219+
ScriptAttribute(
220+
inline="console.log('Hello, world!');",
221+
)
222+
```
223+
216224
"""
217225

218-
src: str
226+
src: str | None = None
227+
inline: str | None = None
219228
asynchronous: bool = False
220229
defer: bool = False
221230
optional_attributes: dict[str, str] = {}
222231

232+
@model_validator(mode="after")
233+
def validate_src_or_inline(self):
234+
if self.src is None and self.inline is None:
235+
raise ValueError("Either 'src' or 'inline' must be provided")
236+
if self.src is not None and self.inline is not None:
237+
raise ValueError("Cannot specify both 'src' and 'inline'")
238+
return self
239+
223240

224241
class Metadata(BaseModel):
225242
"""
@@ -332,13 +349,33 @@ def format_optional_keys(payload: Mapping[str, str | bool | None]) -> str:
332349
tags.append(f"<meta {format_optional_keys(meta_attributes)} />")
333350

334351
for script_definition in self.scripts:
335-
script_attributes: dict[str, str | bool] = {
336-
"src": script_definition.src,
337-
"async": script_definition.asynchronous,
338-
"defer": script_definition.defer,
339-
**script_definition.optional_attributes,
340-
}
341-
tags.append(f"<script {format_optional_keys(script_attributes)}></script>")
352+
if script_definition.inline is not None:
353+
# Inline scripts render the content directly inside the tag
354+
script_attributes: dict[str, str | bool] = {
355+
"async": script_definition.asynchronous,
356+
"defer": script_definition.defer,
357+
**script_definition.optional_attributes,
358+
}
359+
formatted_attrs = format_optional_keys(script_attributes)
360+
if formatted_attrs:
361+
tags.append(
362+
f"<script {formatted_attrs}>{script_definition.inline}</script>"
363+
)
364+
else:
365+
tags.append(f"<script>{script_definition.inline}</script>")
366+
else:
367+
# External scripts use the src attribute
368+
# src is guaranteed to be non-None here due to the model validator
369+
assert script_definition.src is not None
370+
script_attributes = {
371+
"src": script_definition.src,
372+
"async": script_definition.asynchronous,
373+
"defer": script_definition.defer,
374+
**script_definition.optional_attributes,
375+
}
376+
tags.append(
377+
f"<script {format_optional_keys(script_attributes)}></script>"
378+
)
342379

343380
for link_definition in self.links:
344381
if build_metadata and link_definition.add_static_sha:

src/bundle_common.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@ pub fn bundle_common(
219219
debug!("Resolve: {:?}", resolve);
220220
debug!("Bundle mode: {:?}", mode);
221221

222-
let inline_dynamic_imports = matches!(mode, BundleMode::SingleClient | BundleMode::SingleServer);
222+
let inline_dynamic_imports =
223+
matches!(mode, BundleMode::SingleClient | BundleMode::SingleServer);
223224

224225
// https://github.com/rolldown/rolldown/blob/cb5e05c8d9683fd5c190daaad939e5364d7060b2/crates/rolldown_common/src/inner_bundler_options/mod.rs#L41
225226
let bundler_options = BundlerOptions {
@@ -618,8 +619,7 @@ mod tests {
618619
}
619620
"#;
620621

621-
create_test_js_file(temp_path, "lazy.js", lazy_js)
622-
.expect("Failed to create lazy.js file");
622+
create_test_js_file(temp_path, "lazy.js", lazy_js).expect("Failed to create lazy.js file");
623623

624624
// Create a main entry file that imports both modules
625625
let entry_js = r#"
@@ -765,9 +765,7 @@ mod tests {
765765

766766
let css_path = fake_package.join("styles.css");
767767
let mut css_file = File::create(&css_path).unwrap();
768-
css_file
769-
.write_all(b".fake-lib { display: flex; }")
770-
.unwrap();
768+
css_file.write_all(b".fake-lib { display: flex; }").unwrap();
771769

772770
// Create JS that imports the node_modules CSS
773771
let server_js = r#"

0 commit comments

Comments
 (0)