Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: skip serialize option for jsx precompile #241

Merged
merged 3 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 48 additions & 8 deletions src/transpiling/jsx_precompile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub struct JsxPrecompile {
// The import path to import the jsx runtime from. Will be
// `<import_source>/jsx-runtime`.
import_source: String,
// List of HTML elements which should not be serialized
skip_serialize: Option<Vec<String>>,

// Internal state
next_index: usize,
Expand All @@ -38,6 +40,7 @@ impl Default for JsxPrecompile {
next_index: 0,
templates: vec![],
import_source: "react".to_string(),
skip_serialize: None,
import_jsx: None,
import_jsx_ssr: None,
import_jsx_attr: None,
Expand All @@ -47,9 +50,13 @@ impl Default for JsxPrecompile {
}

impl JsxPrecompile {
pub fn new(import_source: String) -> Self {
pub fn new(
import_source: String,
skip_serialize: Option<Vec<String>>,
) -> Self {
Self {
import_source,
skip_serialize,
..JsxPrecompile::default()
}
}
Expand Down Expand Up @@ -339,7 +346,10 @@ fn jsx_member_expr_to_normal(jsx_member_expr: &JSXMemberExpr) -> MemberExpr {
/// we would miss.
/// Moreover, components cannot be safely serialized because there
/// is no specified output format.
fn is_serializable(opening: &JSXOpeningElement) -> bool {
fn is_serializable(
opening: &JSXOpeningElement,
skip_serialize: &Option<Vec<String>>,
) -> bool {
match opening.name.clone() {
// Case: <div />
JSXElementName::Ident(ident) => {
Expand All @@ -352,6 +362,12 @@ fn is_serializable(opening: &JSXOpeningElement) -> bool {
return false;
}

if let Some(skip_elements) = skip_serialize {
if skip_elements.contains(&name) {
return false;
}
}

if opening.attrs.is_empty() {
return true;
}
Expand Down Expand Up @@ -403,6 +419,7 @@ fn serialize_attr(attr_name: &str, value: &str) -> String {

fn merge_serializable_children(
children: &[JSXElementChild],
skip_serialize: &Option<Vec<String>>,
escape_text_children: bool,
) -> (Vec<JSXElementChild>, usize, usize) {
// Do a first pass over children to merge sibling text nodes
Expand Down Expand Up @@ -480,7 +497,7 @@ fn merge_serializable_children(
buf = String::new()
}

if is_serializable(&jsx_el.opening) {
if is_serializable(&jsx_el.opening, skip_serialize) {
serializable_count += 1;
}

Expand Down Expand Up @@ -646,7 +663,7 @@ impl JsxPrecompile {
}
_ => {
let (normalized_children, text_count, serializable_count) =
merge_serializable_children(children, false);
merge_serializable_children(children, &self.skip_serialize, false);

// Merge sibling children when they can be serialized into one
// serialized child. If all children are serializable, we'll
Expand Down Expand Up @@ -962,7 +979,7 @@ impl JsxPrecompile {
dynamic_exprs: &mut Vec<Expr>,
) {
let (normalized_children, _text_count, _serializable_count) =
merge_serializable_children(children, true);
merge_serializable_children(children, &self.skip_serialize, true);

for child in normalized_children {
match child {
Expand Down Expand Up @@ -1022,7 +1039,7 @@ impl JsxPrecompile {
// Case: <div class="foo" {...{ class: "bar"}} />
// Case: <div {...{ class: "foo"}} class="bar"}>foo</div>
// Case: <Foo />
if !is_serializable(&el.opening) {
if !is_serializable(&el.opening, &self.skip_serialize) {
let expr = Expr::Call(self.serialize_jsx_to_call_expr(el));
strings.push("".to_string());
dynamic_exprs.push(expr);
Expand Down Expand Up @@ -1267,7 +1284,7 @@ impl JsxPrecompile {
}

fn serialize_jsx(&mut self, el: &JSXElement) -> Expr {
if is_serializable(&el.opening) {
if is_serializable(&el.opening, &self.skip_serialize) {
// These are now safe to be serialized
// Case: <div foo="1" />
self.next_index += 1;
Expand Down Expand Up @@ -2428,7 +2445,7 @@ const a = _jsx(a.b.c.d, {
#[test]
fn import_source_option_test() {
test_transform(
JsxPrecompile::new("foobar".to_string()),
JsxPrecompile::new("foobar".to_string(), None),
r#"const a = <div>foo</div>;"#,
r#"import { jsxTemplate as _jsxTemplate } from "foobar/jsx-runtime";
const $$_tpl_1 = [
Expand Down Expand Up @@ -2572,6 +2589,29 @@ const a = _jsxTemplate($$_tpl_1);"#,
);
}

#[test]
fn skip_serialization_test() {
test_transform(
JsxPrecompile::new(
"react".to_string(),
Some(vec!["a".to_string(), "img".to_string()]),
),
r#"const a = <div><img src="foo.jpg"/><a href="\#">foo</a></div>"#,
r#"import { jsx as _jsx, jsxTemplate as _jsxTemplate } from "react/jsx-runtime";
const $$_tpl_1 = [
"<div>",
"",
"</div>"
];
const a = _jsxTemplate($$_tpl_1, _jsx("img", {
src: "foo.jpg"
}), _jsx("a", {
href: "\\#",
children: "foo"
}));"#,
);
}

#[track_caller]
fn test_transform(
transform: impl VisitMut,
Expand Down
15 changes: 10 additions & 5 deletions src/transpiling/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ pub struct TranspileOptions {
/// with dynamic content. Defaults to `false`, mutually exclusive with
/// `transform_jsx`.
pub precompile_jsx: bool,
/// List of elements that should not be precompiled when the JSX precompile
/// transform is used.
pub precompile_jsx_skip_elements: Option<Vec<String>>,
/// Should import declarations be transformed to variable declarations using
/// a dynamic import. This is useful for import & export declaration support
/// in script contexts such as the Deno REPL. Defaults to `false`.
Expand All @@ -104,6 +107,7 @@ impl Default for TranspileOptions {
jsx_import_source: None,
transform_jsx: true,
precompile_jsx: false,
precompile_jsx_skip_elements: None,
var_decl_imports: false,
}
}
Expand Down Expand Up @@ -258,6 +262,7 @@ pub fn fold_program(
Optional::new(
as_folder(jsx_precompile::JsxPrecompile::new(
options.jsx_import_source.clone().unwrap_or_default(),
options.precompile_jsx_skip_elements.clone(),
)),
options.jsx_import_source.is_some()
&& !options.transform_jsx
Expand Down Expand Up @@ -1255,6 +1260,7 @@ for (let i = 0; i < testVariable >> 1; i++) callCount++;
let transpile_options = TranspileOptions {
transform_jsx: false,
precompile_jsx: true,
precompile_jsx_skip_elements: Some(vec!["p".to_string()]),
jsx_import_source: Some("react".to_string()),
..Default::default()
};
Expand All @@ -1263,19 +1269,18 @@ for (let i = 0; i < testVariable >> 1; i++) callCount++;
.unwrap()
.text;
let expected1 = r#"import { jsx as _jsx, jsxTemplate as _jsxTemplate } from "react/jsx-runtime";
const $$_tpl_2 = [
"<p>asdf</p>"
];
const $$_tpl_1 = [
"<span>hello</span>foo",
""
];
const a = _jsx(Foo, {
children: _jsxTemplate($$_tpl_1, _jsx(Bar, {
children: _jsxTemplate($$_tpl_2)
children: _jsx("p", {
children: "asdf"
})
}))
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJza"#;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2Vz"#;
assert_eq!(&code[0..expected1.len()], expected1);
}

Expand Down