1#![allow(clippy::redundant_allocation)]
2
3use serde::{Deserialize, Serialize};
4use std::{borrow::Cow, sync::Arc};
5use swc_config::merge::Merge;
6use swc_core::common::comments::Comments;
7use swc_core::common::iter::IdentifyLast;
8use swc_core::common::util::take::Take;
9use swc_core::common::{FileName, Mark, SourceMap, Span, Spanned, DUMMY_SP, SyntaxContext};
10use swc_core::ecma::ast::*;
11use swc_core::ecma::atoms::{Atom};
12use swc_core::ecma::utils::{drop_span, prepend_stmt, quote_ident, ExprFactory, StmtLike};
13use swc_core::ecma::visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
14use swc_core::plugin::errors::HANDLER;
15use swc_ecma_parser::{parse_file_as_expr, Syntax};
16use crate::VNodeType::Component;
17use crate::{
18 inferno_flags::{ChildFlags, VNodeFlags},
19 refresh::options::{deserialize_refresh, RefreshOptions},
20};
21use crate::transformations::transform_attribute::transform_attribute;
22use crate::transformations::lowercase_attrs::requires_lowercasing;
23use crate::transformations::parse_vnode_flag::parse_vnode_flag;
24
25#[cfg(test)]
26mod tests;
27
28#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq, Merge)]
29#[serde(rename_all = "camelCase")]
30#[serde(deny_unknown_fields)]
31pub struct Options {
32 #[serde(skip, default)]
35 pub next: Option<bool>,
36
37 #[serde(default)]
38 pub import_source: Option<String>,
39
40 #[serde(default)]
41 pub development: Option<bool>,
42
43 #[serde(default, deserialize_with = "deserialize_refresh")]
44 pub refresh: Option<RefreshOptions>,
46}
47
48pub fn default_import_source() -> String {
49 "inferno".into()
50}
51
52pub fn parse_expr_for_jsx(
53 cm: &SourceMap,
54 name: &str,
55 src: String,
56 top_level_mark: Mark,
57) -> Arc<Box<Expr>> {
58 let fm = cm.new_source_file(
59 FileName::Custom(format!("<jsx-config-{}.js>", name)).into(),
60 src
61 );
62
63 parse_file_as_expr(
64 &fm,
65 Syntax::default(),
66 Default::default(),
67 None,
68 &mut vec![],
69 )
70 .map_err(|e| {
71 HANDLER.with(|h| {
72 e.into_diagnostic(h)
73 .note("error detected while parsing option for classic jsx transform")
74 .emit()
75 })
76 })
77 .map(drop_span)
78 .map(|mut expr| {
79 apply_mark(&mut expr, top_level_mark);
80 expr
81 })
82 .map(Arc::new)
83 .unwrap_or_else(|()| {
84 panic!(
85 "failed to parse jsx option {}: '{}' is not an expression",
86 name, fm.src,
87 )
88 })
89}
90
91fn apply_mark(e: &mut Expr, mark: Mark) {
92 match e {
93 Expr::Ident(i) => {
94 i.ctxt = i.ctxt.apply_mark(mark);
95 }
96 Expr::Member(MemberExpr { obj, .. }) => {
97 apply_mark(obj, mark);
98 }
99 _ => {}
100 }
101}
102
103fn named_import_exists(import_name: &str, import: &ImportDecl) -> bool {
104 for specifier in &import.specifiers {
105 match specifier {
106 ImportSpecifier::Named(named) => {
107 if import_name == named.local.sym.as_ref() {
108 return true;
109 }
110 }
111 _ => {
112 continue;
113 }
114 }
115 }
116
117 false
118}
119
120fn merge_imports(
121 imports: &Vec<&str>,
122 default_import_src: &str,
123 stmts: &mut Vec<ModuleItem>,
124) -> bool {
125 for stmt in stmts {
126 if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = stmt {
127 if import.src.value == default_import_src {
128 for specifier in &import.specifiers {
129 if let ImportSpecifier::Namespace(_) = specifier {
130 return false;
132 }
133 }
134
135 for import_to_add in imports {
136 let import_exists = named_import_exists(import_to_add, import);
137
138 if !import_exists {
139 import
140 .specifiers
141 .push(ImportSpecifier::Named(ImportNamedSpecifier {
142 span: DUMMY_SP,
143 local: quote_ident!(*import_to_add).into(),
144 imported: None,
145 is_type_only: false,
146 }))
147 }
148 }
149
150 return true;
151 }
152 }
153 }
154
155 false
156}
157
158#[derive(PartialEq)]
159pub enum VNodeType {
160 Element = 0,
161 Component = 1,
162 Fragment = 2,
163}
164
165pub fn jsx<C>(comments: Option<C>, options: Options, unresolved_mark: Mark) -> impl Pass
172where
173 C: Comments,
174{
175 visit_mut_pass(Jsx {
176 unresolved_mark,
177 import_source: options
178 .import_source
179 .unwrap_or_else(default_import_source)
180 .into(),
181 import_create_vnode: None,
182 import_create_component: None,
183 import_create_text_vnode: None,
184 import_create_fragment: None,
185 import_normalize_props: None,
186
187 comments,
188 top_level_node: true,
189 })
190}
191
192struct Jsx<C>
193where
194 C: Comments,
195{
196 unresolved_mark: Mark,
197
198 import_source: Atom,
199
200 import_create_vnode: Option<Ident>,
201 import_create_component: Option<Ident>,
202 import_create_text_vnode: Option<Ident>,
203 import_create_fragment: Option<Ident>,
204 import_normalize_props: Option<Ident>,
205 top_level_node: bool,
206
207 comments: Option<C>,
208}
209
210#[derive(Debug, Default, Clone, PartialEq, Eq)]
211pub struct JsxDirectives {
212 pub import_source: Option<Atom>,
213}
214
215impl<C> Jsx<C>
216where
217 C: Comments,
218{
219 fn inject_runtime<T, F>(&mut self, body: &mut Vec<T>, inject: F)
220 where
221 T: StmtLike,
222 F: Fn(Vec<&str>, &str, &mut Vec<T>),
223 {
224 let mut import_specifiers = vec![];
225
226 if let Some(_local) = self.import_create_vnode.take() {
227 import_specifiers.push("createVNode")
228 }
229 if let Some(_local) = self.import_create_component.take() {
230 import_specifiers.push("createComponentVNode")
231 }
232 if let Some(_local) = self.import_create_text_vnode.take() {
233 import_specifiers.push("createTextVNode")
234 }
235 if let Some(_local) = self.import_normalize_props.take() {
236 import_specifiers.push("normalizeProps")
237 }
238 if let Some(_local) = self.import_create_fragment.take() {
239 import_specifiers.push("createFragment")
240 }
241
242 if !import_specifiers.is_empty() {
243 inject(import_specifiers, &self.import_source, body);
244 }
245 }
246
247 fn set_local_import_refs(&mut self, stmts: &mut Vec<ModuleItem>) {
248 for stmt in stmts {
249 if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = stmt {
250 if import.src.value == self.import_source {
251 for specifier in import.specifiers.iter_mut() {
252 match specifier {
253 ImportSpecifier::Named(named_import) => {
254 if named_import.local.sym == "createVNode" {
255 self.import_create_vnode
256 .get_or_insert(named_import.local.clone());
257 } else if named_import.local.sym == "createComponentVNode" {
258 self.import_create_component
259 .get_or_insert(named_import.local.clone());
260 } else if named_import.local.sym == "createTextVNode" {
261 self.import_create_text_vnode
262 .get_or_insert(named_import.local.clone());
263 } else if named_import.local.sym == "createFragment" {
264 self.import_create_fragment
265 .get_or_insert(named_import.local.clone());
266 } else if named_import.local.sym == "normalizeProps" {
267 self.import_normalize_props
268 .get_or_insert(named_import.local.clone());
269 }
270 }
271 _ => continue,
272 }
273 }
274
275 return;
276 }
277 }
278 }
279 }
280
281 fn jsx_frag_to_expr(&mut self, el: JSXFragment) -> Expr {
282 let span = el.span();
283
284 if let Some(comments) = &self.comments {
285 comments.add_pure_comment(span.lo);
286 }
287
288 let fragment = self
289 .import_create_fragment
290 .get_or_insert_with(|| quote_ident!("createFragment").into())
291 .clone();
292
293 let mut children_requires_normalization: bool = false;
294 let mut parent_can_be_keyed: bool = false;
295 let mut children_count: u16 = 0;
296
297 let mut children = vec![];
298 for child in el.children {
299 let child_expr = Some(match child {
300 JSXElementChild::JSXText(text) => {
301 let value = jsx_text_to_str(text.value);
303 let s = Str {
304 span: text.span,
305 raw: None,
306 value,
307 };
308
309 if s.value.is_empty() {
310 continue;
311 }
312
313 ExprOrSpread {
314 spread: None,
315 expr: Box::new(Expr::Call(CallExpr {
316 span: DUMMY_SP,
317 callee: self
318 .import_create_text_vnode
319 .get_or_insert_with(|| quote_ident!("createTextVNode").into())
320 .clone()
321 .as_callee(),
322 args: vec![s.as_arg()],
323 ..Default::default()
324 })),
325 }
326 }
327 JSXElementChild::JSXExprContainer(JSXExprContainer {
328 expr: JSXExpr::Expr(e),
329 ..
330 }) => {
331 children_requires_normalization = true;
332 parent_can_be_keyed = false;
333 e.as_arg()
334 }
335 JSXElementChild::JSXExprContainer(JSXExprContainer {
336 expr: JSXExpr::JSXEmptyExpr(..),
337 ..
338 }) => continue,
339 JSXElementChild::JSXElement(el) => {
340 if !parent_can_be_keyed && !children_requires_normalization {
341 parent_can_be_keyed = Self::does_children_have_key_defined(&el);
343 }
344 self.jsx_elem_to_expr(*el).as_arg()
345 }
346 JSXElementChild::JSXFragment(el) => self.jsx_frag_to_expr(el).as_arg(),
347 JSXElementChild::JSXSpreadChild(JSXSpreadChild { span, expr, .. }) => {
348 ExprOrSpread {
349 spread: Some(span),
350 expr,
351 }
352 }
353 });
354
355 children_count += 1;
356
357 children.push(child_expr)
358 }
359
360 let child_flags;
361
362 if !children_requires_normalization {
363 if children_count >= 1 {
364 if parent_can_be_keyed {
365 child_flags = ChildFlags::HasKeyedChildren;
366 } else {
367 child_flags = ChildFlags::HasNonKeyedChildren;
368 }
369 } else {
370 child_flags = ChildFlags::HasInvalidChildren;
371 }
372 } else {
373 child_flags = ChildFlags::UnknownChildren;
374 };
375
376 Expr::Call(CallExpr {
377 span,
378 callee: fragment.as_callee(),
379 args: create_fragment_vnode_args(children, false, child_flags as u16, None, None),
380 type_args: None,
381 ..Default::default()
382 })
383 }
384
385 fn jsx_elem_to_expr(&mut self, el: JSXElement) -> Expr {
386 let top_level_node = self.top_level_node;
387 let span = el.span();
388 self.top_level_node = false;
389
390 let name_span: Span = el.opening.name.span();
391 let name_expr;
392 let mut mut_flags: u16;
393 let vnode_kind: VNodeType;
394
395 match el.opening.name {
396 JSXElementName::Ident(ident) => {
397 if ident.sym == "this" {
398 vnode_kind = Component;
399 mut_flags = VNodeFlags::ComponentUnknown as u16;
400 name_expr = Expr::This(ThisExpr { span: name_span });
401 } else if is_component_vnode(&ident) {
402 if ident.sym == "Fragment" {
403 vnode_kind = VNodeType::Fragment;
404 mut_flags = VNodeFlags::ComponentUnknown as u16;
405 name_expr = Expr::Ident(Ident::new("createFragment".into(), ident.span, Default::default()));
406 } else {
407 vnode_kind = Component;
408 mut_flags = VNodeFlags::ComponentUnknown as u16;
409 name_expr = Expr::Ident(ident)
410 }
411 } else {
412 vnode_kind = VNodeType::Element;
413 mut_flags = parse_vnode_flag(&ident.sym);
414 name_expr = Expr::Lit(Lit::Str(Str {
415 span: name_span,
416 raw: None,
417 value: ident.sym,
418 }))
419 }
420 }
421 JSXElementName::JSXNamespacedName(_) => {
422 HANDLER.with(|handler| {
423 handler
424 .struct_span_err(name_span, "JSX Namespace is disabled")
425 .emit()
426 });
427
428 return Expr::Invalid(Invalid { span: DUMMY_SP });
429 }
430 JSXElementName::JSXMemberExpr(JSXMemberExpr { obj, prop, .. }) => {
431 vnode_kind = Component;
432 mut_flags = VNodeFlags::ComponentUnknown as u16;
433
434 fn convert_obj(obj: JSXObject) -> Box<Expr> {
435 let span = obj.span();
436
437 (match obj {
438 JSXObject::Ident(i) => {
439 if i.sym == "this" {
440 Expr::This(ThisExpr { span })
441 } else {
442 Expr::Ident(i)
443 }
444 }
445 JSXObject::JSXMemberExpr(e) => Expr::Member(MemberExpr {
446 span,
447 obj: convert_obj(e.obj),
448 prop: MemberProp::Ident(e.prop),
449 }),
450 })
451 .into()
452 }
453 name_expr = Expr::Member(MemberExpr {
454 span: name_span,
455 obj: convert_obj(obj),
456 prop: MemberProp::Ident(prop.clone()),
457 })
458 }
459 }
460
461 if let Some(comments) = &self.comments {
462 comments.add_pure_comment(span.lo);
463 }
464
465 let mut props_obj = ObjectLit {
466 span: DUMMY_SP,
467 props: vec![],
468 };
469
470 let mut key_prop = None;
471 let mut ref_prop = None;
472 let mut component_refs: Option<ObjectLit> = None;
473
474 let mut class_name_param: Option<Box<Expr>> = None;
475 let mut has_text_children: bool = false;
476 let mut has_keyed_children: bool = false;
477 let mut has_non_keyed_children: bool = false;
478 let mut children_known: bool = false;
479 let mut needs_normalization: bool = false;
480 let mut has_re_create_flag: bool = false;
481 let mut child_flags_override_param = None;
482 let mut flags_override_param = None;
483 let mut content_editable_props: bool = false;
484 let mut prop_children: Option<Box<Expr>> = None;
485
486 for attr in el.opening.attrs {
487 match attr {
488 JSXAttrOrSpread::JSXAttr(attr) => {
489 match attr.name {
491 JSXAttrName::Ident(i) => {
492 if i.sym == "class" || i.sym == "className" {
494 if vnode_kind == VNodeType::Element {
495 if let Some(v) = attr.value {
496 class_name_param = jsx_attr_value_to_expr(v)
497 }
498
499 continue;
500 }
501 } else if i.sym == "onDoubleClick" {
502 props_obj
503 .props
504 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
505 KeyValueProp {
506 key: PropName::Ident(IdentName::new(
507 "onDblClick".into(),
508 span
509 )),
510 value: match attr.value {
511 Some(v) => jsx_attr_value_to_expr(v)
512 .expect("empty expression?"),
513 None => true.into(),
514 },
515 },
516 ))));
517 continue;
518 } else if i.sym == "key" {
519 key_prop = attr
520 .value
521 .and_then(jsx_attr_value_to_expr)
522 .map(|expr| expr.as_arg());
523
524 if key_prop.is_none() {
525 HANDLER.with(|handler| {
526 handler
527 .struct_span_err(
528 i.span,
529 "The value of property 'key' should not be \
530 empty",
531 )
532 .emit();
533 });
534 }
535
536 continue;
537 } else if i.sym == "ref" {
538 ref_prop = attr
539 .value
540 .and_then(jsx_attr_value_to_expr)
541 .map(|expr| expr.as_arg());
542
543 if ref_prop.is_none() {
544 HANDLER.with(|handler| {
545 handler
546 .struct_span_err(
547 i.span,
548 "The value of property 'ref' should not be \
549 empty",
550 )
551 .emit();
552 });
553 }
554
555 continue;
556 } else if i.sym == "$ChildFlag" {
557 child_flags_override_param = attr
558 .value
559 .and_then(jsx_attr_value_to_expr)
560 .map(|expr| expr.as_arg());
561
562 if child_flags_override_param.is_none() {
563 HANDLER.with(|handler| {
564 handler
565 .struct_span_err(
566 i.span,
567 "The value of property '$ChildFlag' should \
568 not be empty",
569 )
570 .emit();
571 });
572 }
573
574 children_known = true;
575 continue;
576 } else if i.sym == "$HasVNodeChildren" {
577 children_known = true;
578 continue;
579 } else if i.sym == "$Flags" {
580 flags_override_param = attr
581 .value
582 .and_then(jsx_attr_value_to_expr)
583 .map(|expr| expr.as_arg());
584
585 if flags_override_param.is_none() {
586 HANDLER.with(|handler| {
587 handler
588 .struct_span_err(
589 i.span,
590 "The value of property '$Flags' should not be \
591 empty",
592 )
593 .emit();
594 });
595 }
596
597 continue;
598 } else if i.sym == "$HasTextChildren" {
599 children_known = true;
600 has_text_children = true;
601 continue;
602 } else if i.sym == "$HasNonKeyedChildren" {
603 children_known = true;
604 has_non_keyed_children = true;
605 continue;
606 } else if i.sym == "$HasKeyedChildren" {
607 children_known = true;
608 has_keyed_children = true;
609 continue;
610 } else if i.sym == "$ReCreate" {
611 has_re_create_flag = true;
612 continue;
613 }
614
615 if i.sym.to_ascii_lowercase() == "contenteditable" {
616 content_editable_props = true;
617 } else if i.sym == "children" {
618 if !el.children.is_empty() {
619 continue;
621 }
622
623 prop_children = match attr.value {
624 Some(v) => jsx_attr_value_to_expr(v),
625 None => continue,
626 };
627
628 continue;
629 } else if vnode_kind == Component
630 && i.sym.as_ref().starts_with("onComponent")
631 {
632 if let Some(v) = attr.value {
633 if component_refs.is_none() {
634 component_refs = Some(ObjectLit {
635 span: DUMMY_SP,
636 props: vec![],
637 })
638 };
639
640 if let Some(some_component_refs) = component_refs.as_mut() {
641 some_component_refs.props.push(PropOrSpread::Prop(
642 Box::new(Prop::KeyValue(KeyValueProp {
643 key: PropName::Ident(i),
644 value: jsx_attr_value_to_expr(v)
645 .expect("empty expression container?"),
646 })),
647 ));
648 }
649 };
650
651 continue;
652 }
653
654 let value = match attr.value {
655 Some(v) => {
656 jsx_attr_value_to_expr(v).expect("empty expression container?")
657 }
658 None => true.into(),
659 };
660
661 let converted_prop_name = if requires_lowercasing(&i.sym) {
662 PropName::Ident(IdentName {
663 span: i.span,
664 sym: i.sym.to_lowercase().into()
665 })
666 } else {
667 let converted_sym = transform_attribute(&i.sym);
668
669 if converted_sym.contains('-') || converted_sym.contains(':') {
670 PropName::Str(Str {
671 span: i.span,
672 raw: None,
673 value: converted_sym.into(),
674 })
675 } else {
676 PropName::Ident(IdentName {
677 span: i.span,
678 sym: converted_sym.into()
679 })
680 }
681 };
682
683 props_obj
684 .props
685 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
686 key: converted_prop_name,
687 value,
688 }))));
689 }
690 JSXAttrName::JSXNamespacedName(JSXNamespacedName { ns, name, .. }) => {
691 let value = match attr.value {
692 Some(v) => {
693 jsx_attr_value_to_expr(v).expect("empty expression container?")
694 }
695 None => true.into(),
696 };
697
698 let str_value = format!("{}:{}", ns.sym, name.sym);
699 let key = Str {
700 span,
701 raw: None,
702 value: str_value.into(),
703 };
704 let key = PropName::Str(key);
705
706 props_obj
707 .props
708 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
709 key,
710 value,
711 }))));
712 }
713 }
714 }
715 JSXAttrOrSpread::SpreadElement(attr) => match *attr.expr {
716 Expr::Object(obj) => {
717 needs_normalization = true;
718 props_obj.props.extend(obj.props);
719 }
720 _ => {
721 needs_normalization = true;
722 props_obj.props.push(PropOrSpread::Spread(attr));
723 }
724 },
725 }
726 }
727
728 let mut children_requires_normalization: bool = false;
729 let mut children_found_text: bool = false;
730 let mut parent_can_be_keyed: bool = false;
731 let mut children_count: u16 = 0;
732
733 let mut children = vec![];
734
735 for child in el.children {
736 let child_expr = Some(match child {
737 JSXElementChild::JSXText(text) => {
738 let value = jsx_text_to_str(text.value);
740 let s = Str {
741 span: text.span,
742 raw: None,
743 value,
744 };
745
746 if s.value.is_empty() {
747 continue;
748 }
749
750 if vnode_kind == VNodeType::Fragment {
751 ExprOrSpread {
752 spread: None,
753 expr: Box::new(Expr::Call(CallExpr {
754 span: DUMMY_SP,
755 ctxt: SyntaxContext::empty().apply_mark(self.unresolved_mark),
756 callee: self
757 .import_create_text_vnode
758 .get_or_insert_with(|| quote_ident!("createTextVNode").into())
759 .clone()
760 .as_callee(),
761 args: vec![s.as_arg()],
762 type_args: Default::default(),
763 })),
764 }
765 } else {
766 children_found_text = true;
767 Lit::Str(s).as_arg()
768 }
769 }
770 JSXElementChild::JSXExprContainer(JSXExprContainer {
771 expr: JSXExpr::Expr(e),
772 ..
773 }) => {
774 children_requires_normalization = true;
775 parent_can_be_keyed = false;
776 e.as_arg()
777 }
778 JSXElementChild::JSXExprContainer(JSXExprContainer {
779 expr: JSXExpr::JSXEmptyExpr(..),
780 ..
781 }) => continue,
782 JSXElementChild::JSXElement(el) => {
783 if vnode_kind != Component
784 && !parent_can_be_keyed
785 && !children_known
786 && !children_requires_normalization
787 {
788 parent_can_be_keyed = Self::does_children_have_key_defined(&el);
790 }
791 self.jsx_elem_to_expr(*el).as_arg()
792 }
793 JSXElementChild::JSXFragment(el) => self.jsx_frag_to_expr(el).as_arg(),
794 JSXElementChild::JSXSpreadChild(JSXSpreadChild { span, expr, .. }) => {
795 ExprOrSpread {
796 spread: Some(span),
797 expr,
798 }
799 }
800 });
801
802 children_count += 1;
803
804 children.push(child_expr)
805 }
806
807 if children_found_text {
808 match children_count {
809 1 => has_text_children = true,
810 _ => {
811 for i in 0..children.len() {
812 let child = &children[i];
813
814 match child {
815 Some(v) => {
816 if let Expr::Lit(Lit::Str(text)) = &*v.expr {
817 children[i] = Some(ExprOrSpread {
818 spread: None,
819 expr: Box::new(Expr::Call(CallExpr {
820 span: DUMMY_SP,
821 ctxt: SyntaxContext::empty().apply_mark(self.unresolved_mark),
822 callee: self
823 .import_create_text_vnode
824 .get_or_insert_with(|| {
825 quote_ident!("createTextVNode").into()
826 })
827 .clone()
828 .as_callee(),
829 args: vec![text.clone().as_arg()],
830 type_args: Default::default(),
831 })),
832 })
833 }
834 }
835 _ => continue,
836 }
837 }
838 }
839 }
840 }
841
842 parent_can_be_keyed =
843 children_count > 1 && parent_can_be_keyed && !children_requires_normalization;
844 let parent_can_be_non_keyed =
845 children_count > 1 && !parent_can_be_keyed && !children_requires_normalization;
846
847 let child_flags: ChildFlags;
848
849 if !children_requires_normalization || children_known {
850 if has_keyed_children || parent_can_be_keyed {
851 child_flags = ChildFlags::HasKeyedChildren;
852 } else if has_non_keyed_children || parent_can_be_non_keyed {
853 child_flags = ChildFlags::HasNonKeyedChildren;
854 } else if children_count == 1 {
855 if has_text_children {
856 child_flags = ChildFlags::HasTextChildren;
857 } else if vnode_kind == VNodeType::Fragment {
858 child_flags = ChildFlags::HasNonKeyedChildren;
859 } else {
860 child_flags = ChildFlags::HasVNodeChildren;
861 }
862 } else {
863 child_flags = ChildFlags::HasInvalidChildren
864 }
865 } else if has_keyed_children {
866 child_flags = ChildFlags::HasKeyedChildren;
867 } else if has_non_keyed_children {
868 child_flags = ChildFlags::HasNonKeyedChildren;
869 } else if has_text_children {
870 child_flags = ChildFlags::HasTextChildren;
871 } else {
872 child_flags = ChildFlags::UnknownChildren;
873 }
874
875 if vnode_kind == Component {
876 match children.len() {
877 0 => {
878 match prop_children {
879 Some(some_prop_children) => {
880 props_obj
881 .props
882 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
883 key: PropName::Ident(quote_ident!("children")),
884 value: some_prop_children,
885 }))));
886 }
887 None => {
888 }
890 }
891 }
892 1 if children[0].as_ref().unwrap().spread.is_none() => {
893 props_obj
894 .props
895 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
896 key: PropName::Ident(quote_ident!("children")),
897 value: children.take().into_iter().next().flatten().unwrap().expr,
898 }))));
899 }
900 _ => {
901 props_obj
902 .props
903 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
904 key: PropName::Ident(quote_ident!("children")),
905 value: Box::new(Expr::Array(ArrayLit {
906 span: DUMMY_SP,
907 elems: children.take(),
908 })),
909 }))));
910 }
911 }
912 } else {
913 if children.is_empty() {
916 match prop_children {
917 Some(some_prop_children) => children.push(Some(ExprOrSpread {
918 spread: None,
919 expr: some_prop_children,
920 })),
921 None => {
922 }
924 }
925 }
926 }
927
928 self.top_level_node = top_level_node;
929
930 if has_re_create_flag {
931 mut_flags |= VNodeFlags::ReCreate as u16;
932 }
933 if content_editable_props {
934 mut_flags |= VNodeFlags::ContentEditable as u16;
935 }
936
937 let flags_expr = match flags_override_param {
938 None => Box::new(Expr::Lit(Lit::Num(Number {
939 span: DUMMY_SP,
940 raw: None,
941 value: mut_flags as f64,
942 })))
943 .as_arg(),
944 Some(v) => v,
945 };
946
947 let create_method = if vnode_kind == Component {
948 self.import_create_component
949 .get_or_insert_with(|| quote_ident!("createComponentVNode").into())
950 .clone()
951 } else if vnode_kind == VNodeType::Element {
952 self.import_create_vnode
953 .get_or_insert_with(|| quote_ident!("createVNode").into())
954 .clone()
955 } else {
956 self.import_create_fragment
957 .get_or_insert_with(|| quote_ident!("createFragment").into())
958 .clone()
959 };
960
961 let create_method_args = if vnode_kind == Component {
962 if let Some(some_refs) = component_refs {
966 create_component_vnode_args(
967 flags_expr,
968 name_expr,
969 props_obj,
970 key_prop,
971 Some(some_refs.as_arg()),
972 )
973 } else {
974 create_component_vnode_args(flags_expr, name_expr, props_obj, key_prop, ref_prop)
975 }
976 } else if vnode_kind == VNodeType::Element {
977 create_vnode_args(
978 flags_expr,
979 name_expr,
980 class_name_param,
981 children,
982 child_flags as u16,
983 child_flags_override_param,
984 props_obj,
985 key_prop,
986 ref_prop,
987 )
988 } else {
989 create_fragment_vnode_args(
990 children,
991 has_non_keyed_children
992 || has_keyed_children
993 || child_flags_override_param.is_some(),
994 child_flags as u16,
995 child_flags_override_param,
996 key_prop,
997 )
998 };
999
1000 let create_expr = Expr::Call(CallExpr {
1001 span,
1002 ctxt: SyntaxContext::empty().apply_mark(self.unresolved_mark),
1003 callee: create_method.as_callee(),
1004 args: create_method_args,
1005 type_args: Default::default(),
1006 });
1007
1008 if needs_normalization {
1009 return Expr::Call(CallExpr {
1010 span,
1011 ctxt: SyntaxContext::empty().apply_mark(self.unresolved_mark),
1012 callee: self
1013 .import_normalize_props
1014 .get_or_insert_with(|| quote_ident!("normalizeProps").into())
1015 .clone()
1016 .as_callee(),
1017 args: vec![create_expr.as_arg()],
1018 type_args: Default::default(),
1019 });
1020 }
1021
1022 create_expr
1023 }
1024
1025 fn does_children_have_key_defined(el: &JSXElement) -> bool {
1026 for attr in &el.opening.attrs {
1027 match attr {
1028 JSXAttrOrSpread::JSXAttr(attr) => {
1029 match &attr.name {
1031 JSXAttrName::Ident(i) => {
1032 if i.sym == "key" {
1033 return true;
1034 }
1035 }
1036 JSXAttrName::JSXNamespacedName(_) => {
1037 continue;
1038 }
1039 }
1040 }
1041 JSXAttrOrSpread::SpreadElement(_attr) => {
1042 continue;
1043 }
1044 }
1045 }
1046
1047 false
1048 }
1049}
1050
1051#[inline(always)]
1052fn create_vnode_args(
1053 flags: ExprOrSpread,
1054 name: Expr,
1055 class_name: Option<Box<Expr>>,
1056 mut children: Vec<Option<ExprOrSpread>>,
1057 child_flags: u16,
1058 child_flags_override_param: Option<ExprOrSpread>,
1059 props: ObjectLit,
1060 key: Option<ExprOrSpread>,
1061 refs: Option<ExprOrSpread>,
1062) -> Vec<ExprOrSpread> {
1063 let mut args: Vec<ExprOrSpread> = vec![flags, name.as_arg()];
1064
1065 let has_children = !children.is_empty();
1066 let has_child_flags = child_flags_override_param.is_some()
1067 || child_flags != (ChildFlags::HasInvalidChildren as u16);
1068 let has_props = !props.props.is_empty();
1069 let has_key = key.is_some();
1070 let has_ref = refs.is_some();
1071
1072 match class_name {
1073 None => {
1074 if has_children || has_child_flags || has_props || has_key || has_ref {
1075 args.push(Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))).as_arg());
1076 }
1077 }
1078 Some(some_class_name) => {
1079 args.push(some_class_name.as_arg());
1080 }
1081 }
1082
1083 match children.len() {
1084 0 => {
1085 if has_child_flags || has_props || has_key || has_ref {
1086 args.push(Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))).as_arg());
1087 }
1088 }
1089 1 => args.push(
1090 children
1091 .take()
1092 .into_iter()
1093 .next()
1094 .flatten()
1095 .unwrap()
1096 .expr
1097 .as_arg(),
1098 ),
1099 _ => args.push(
1100 Box::new(Expr::Array(ArrayLit {
1101 span: DUMMY_SP,
1102 elems: children.take(),
1103 }))
1104 .as_arg(),
1105 ),
1106 }
1107
1108 if has_child_flags {
1109 match child_flags_override_param {
1110 Some(some_child_flags_override_param) => {
1111 args.push(some_child_flags_override_param);
1112 }
1113 None => args.push(
1114 Box::new(Expr::Lit(Lit::Num(Number {
1115 span: DUMMY_SP,
1116 raw: None,
1117 value: child_flags as f64,
1118 })))
1119 .as_arg(),
1120 ),
1121 }
1122 } else if has_props || has_key || has_ref {
1123 args.push(
1124 Box::new(Expr::Lit(Lit::Num(Number {
1125 span: DUMMY_SP,
1126 raw: None,
1127 value: (ChildFlags::HasInvalidChildren as u16) as f64,
1128 })))
1129 .as_arg(),
1130 );
1131 }
1132
1133 if has_props {
1134 args.push(props.as_arg());
1135 } else if has_key || has_ref {
1136 args.push(Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))).as_arg());
1137 }
1138
1139 match key {
1140 None => {
1141 if has_ref {
1142 args.push(Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))).as_arg());
1143 }
1144 }
1145 Some(some_key) => {
1146 args.push(some_key);
1147 }
1148 }
1149
1150 match refs {
1151 None => {}
1152 Some(some_refs) => {
1153 args.push(some_refs);
1154 }
1155 }
1156
1157 args
1158}
1159
1160#[inline(always)]
1161fn create_component_vnode_args(
1162 flags: ExprOrSpread,
1163 name: Expr,
1164 props_literal: ObjectLit,
1165 key: Option<ExprOrSpread>,
1166 refs: Option<ExprOrSpread>,
1167) -> Vec<ExprOrSpread> {
1168 let mut args: Vec<ExprOrSpread> = vec![flags, name.as_arg()];
1169
1170 if props_literal.props.is_empty() {
1171 if key.is_some() || refs.is_some() {
1172 args.push(Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))).as_arg());
1173 }
1174 } else {
1175 args.push(props_literal.as_arg());
1176 }
1177
1178 match key {
1179 None => {
1180 if refs.is_some() {
1181 args.push(Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))).as_arg());
1182 }
1183 }
1184 Some(some_key) => {
1185 args.push(some_key);
1186 }
1187 }
1188
1189 match refs {
1190 None => {}
1191 Some(some_ref) => {
1192 args.push(some_ref);
1193 }
1194 }
1195
1196 args
1197}
1198
1199#[inline(always)]
1200fn create_fragment_vnode_args(
1201 mut children: Vec<Option<ExprOrSpread>>,
1202 children_shape_is_user_defined: bool,
1203 child_flags: u16,
1204 child_flags_override_param: Option<ExprOrSpread>,
1205 key: Option<ExprOrSpread>,
1206) -> Vec<ExprOrSpread> {
1207 let mut args: Vec<ExprOrSpread> = vec![];
1208 let has_child_flags = child_flags_override_param.is_some()
1209 || child_flags != (ChildFlags::HasInvalidChildren as u16);
1210 let has_key = key.is_some();
1211
1212 match children.len() {
1213 0 => {
1214 if has_child_flags || has_key {
1215 args.push(Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))).as_arg());
1216 }
1217 }
1218 1 => {
1219 if children_shape_is_user_defined || child_flags == ChildFlags::UnknownChildren as u16 {
1220 args.push(
1221 children
1222 .take()
1223 .into_iter()
1224 .next()
1225 .flatten()
1226 .unwrap()
1227 .expr
1228 .as_arg(),
1229 );
1230 } else {
1231 args.push(
1232 Box::new(Expr::Array(ArrayLit {
1233 span: DUMMY_SP,
1234 elems: children.take(),
1235 }))
1236 .as_arg(),
1237 );
1238 }
1239 }
1240 _ => args.push(
1241 Box::new(Expr::Array(ArrayLit {
1242 span: DUMMY_SP,
1243 elems: children.take(),
1244 }))
1245 .as_arg(),
1246 ),
1247 }
1248
1249 if has_child_flags {
1250 match child_flags_override_param {
1251 Some(some_child_flags_override_param) => {
1252 args.push(some_child_flags_override_param);
1253 }
1254 None => args.push(
1255 Box::new(Expr::Lit(Lit::Num(Number {
1256 span: DUMMY_SP,
1257 raw: None,
1258 value: child_flags as f64,
1259 })))
1260 .as_arg(),
1261 ),
1262 }
1263 } else if has_key {
1264 args.push(
1265 Box::new(Expr::Lit(Lit::Num(Number {
1266 span: DUMMY_SP,
1267 raw: None,
1268 value: (ChildFlags::HasInvalidChildren as u16) as f64,
1269 })))
1270 .as_arg(),
1271 );
1272 }
1273
1274 match key {
1275 None => {}
1276 Some(some_key) => {
1277 args.push(some_key);
1278 }
1279 }
1280
1281 args
1282}
1283
1284impl<C> VisitMut for Jsx<C>
1285where
1286 C: Comments,
1287{
1288 noop_visit_mut_type!();
1289
1290 fn visit_mut_expr(&mut self, expr: &mut Expr) {
1291 let top_level_node = self.top_level_node;
1292 let mut did_work = false;
1293
1294 if let Expr::JSXElement(el) = expr {
1295 did_work = true;
1296 *expr = self.jsx_elem_to_expr(*el.take());
1298 } else if let Expr::JSXFragment(frag) = expr {
1299 did_work = true;
1301 *expr = self.jsx_frag_to_expr(frag.take());
1302 } else if let Expr::Paren(ParenExpr {
1303 expr: inner_expr, ..
1304 }) = expr
1305 {
1306 if let Expr::JSXElement(el) = &mut **inner_expr {
1307 did_work = true;
1308 *expr = self.jsx_elem_to_expr(*el.take());
1309 } else if let Expr::JSXFragment(frag) = &mut **inner_expr {
1310 did_work = true;
1312 *expr = self.jsx_frag_to_expr(frag.take());
1313 }
1314 }
1315
1316 if did_work {
1317 self.top_level_node = false;
1318 }
1319
1320 expr.visit_mut_children_with(self);
1321
1322 self.top_level_node = top_level_node;
1323 }
1324
1325 fn visit_mut_module(&mut self, module: &mut Module) {
1326 self.set_local_import_refs(&mut module.body);
1327
1328 module.visit_mut_children_with(self);
1329
1330 self.inject_runtime(&mut module.body, |imports, default_import_src, stmts| {
1331 if merge_imports(&imports, default_import_src, stmts) {
1333 return;
1334 }
1335
1336 let specifiers: Vec<ImportSpecifier> = imports
1338 .into_iter()
1339 .map(|imported| {
1340 ImportSpecifier::Named(ImportNamedSpecifier {
1341 span: DUMMY_SP,
1342 local: quote_ident!(imported).into(),
1343 imported: None,
1344 is_type_only: false,
1345 })
1346 })
1347 .collect();
1348
1349 prepend_stmt(
1350 stmts,
1351 ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1352 span: DUMMY_SP,
1353 specifiers,
1354 src: Str {
1355 span: DUMMY_SP,
1356 raw: None,
1357 value: default_import_src.into(),
1358 }
1359 .into(),
1360 type_only: Default::default(),
1361 with: Default::default(),
1362 phase: Default::default(),
1363 })),
1364 )
1365 });
1366 }
1367
1368 fn visit_mut_script(&mut self, script: &mut Script) {
1369 script.visit_mut_children_with(self);
1370
1371 let mark = self.unresolved_mark;
1372 self.inject_runtime(&mut script.body, |imports, src, stmts| {
1373 prepend_stmt(stmts, add_require(imports, src, mark))
1374 });
1375 }
1376}
1377
1378fn add_require(imports: Vec<&str>, src: &str, unresolved_mark: Mark) -> Stmt {
1379 Stmt::Decl(Decl::Var(Box::new(VarDecl {
1380 span: DUMMY_SP,
1381 ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
1382 kind: VarDeclKind::Const,
1383 declare: false,
1384 decls: vec![VarDeclarator {
1385 span: DUMMY_SP,
1386 name: Pat::Object(ObjectPat {
1387 span: DUMMY_SP,
1388 props: imports
1389 .into_iter()
1390 .map(|imported| {
1391 ObjectPatProp::Assign(AssignPatProp {
1392 span: DUMMY_SP,
1393 key: BindingIdent {
1394 id: Ident {
1395 span: DUMMY_SP,
1396 ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
1397 sym: imported.into(),
1398 optional: false,
1399 },
1400 type_ann: None,
1401 },
1402 value: None,
1403 })
1404 })
1405 .collect(),
1406 optional: false,
1407 type_ann: None,
1408 }),
1409 init: Some(Box::new(Expr::Call(CallExpr {
1411 span: DUMMY_SP,
1412 ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
1413 callee: Callee::Expr(Box::new(Expr::Ident(Ident {
1414 ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
1415 sym: "require".into(),
1416 optional: false,
1417 ..Default::default()
1418 }))),
1419 args: vec![ExprOrSpread {
1420 spread: None,
1421 expr: Box::new(Expr::Lit(Lit::Str(Str {
1422 span: DUMMY_SP,
1423 value: src.into(),
1424 raw: None,
1425 }))),
1426 }],
1427 type_args: None,
1428 }))),
1429 definite: false,
1430 }],
1431 })))
1432}
1433
1434#[inline]
1435fn is_component_vnode(i: &Ident) -> bool {
1436 i.as_ref().starts_with(|c: char| c.is_ascii_uppercase())
1438}
1439
1440#[inline]
1441fn jsx_text_to_str(t: Atom) -> Atom {
1442 let mut buf = String::new();
1443 let replaced = t.replace('\t', " ");
1444
1445 for (is_last, (i, line)) in replaced.lines().enumerate().identify_last() {
1446 if line.is_empty() {
1447 continue;
1448 }
1449 let line = Cow::from(line);
1450 let line = if i != 0 {
1451 Cow::Borrowed(line.trim_start_matches(' '))
1452 } else {
1453 line
1454 };
1455 let line = if is_last {
1456 line
1457 } else {
1458 Cow::Borrowed(line.trim_end_matches(' '))
1459 };
1460 if line.len() == 0 {
1461 continue;
1462 }
1463 if i != 0 && !buf.is_empty() {
1464 buf.push(' ')
1465 }
1466 buf.push_str(&line);
1467 }
1468 buf.into()
1469}
1470
1471fn jsx_attr_value_to_expr(v: JSXAttrValue) -> Option<Box<Expr>> {
1472 Some(match v {
1473 JSXAttrValue::Lit(Lit::Str(s)) => {
1474 let value = transform_jsx_attr_str(&s.value);
1475
1476 Box::new(Expr::Lit(Lit::Str(Str {
1477 span: s.span,
1478 raw: None,
1479 value: value.into(),
1480 })))
1481 }
1482 JSXAttrValue::Lit(lit) => Box::new(lit.into()),
1483 JSXAttrValue::JSXExprContainer(e) => match e.expr {
1484 JSXExpr::JSXEmptyExpr(_) => None?,
1485 JSXExpr::Expr(e) => e,
1486 },
1487 JSXAttrValue::JSXElement(e) => Box::new(Expr::JSXElement(e)),
1488 JSXAttrValue::JSXFragment(f) => Box::new(Expr::JSXFragment(f)),
1489 })
1490}
1491
1492fn transform_jsx_attr_str(v: &str) -> String {
1493 let single_quote = false;
1494 let mut buf = String::with_capacity(v.len());
1495 let mut iter = v.chars().peekable();
1496
1497 while let Some(c) = iter.next() {
1498 match c {
1499 '\u{0008}' => buf.push_str("\\b"),
1500 '\u{000c}' => buf.push_str("\\f"),
1501 ' ' => buf.push(' '),
1502
1503 '\n' | '\r' | '\t' => {
1504 buf.push(' ');
1505
1506 while let Some(' ') = iter.peek() {
1507 iter.next();
1508 }
1509 }
1510 '\u{000b}' => buf.push_str("\\v"),
1511 '\0' => buf.push_str("\\x00"),
1512
1513 '\'' if single_quote => buf.push_str("\\'"),
1514 '"' if !single_quote => buf.push('\"'),
1515
1516 '\x01'..='\x0f' | '\x10'..='\x1f' => {
1517 buf.push(c);
1518 }
1519
1520 '\x20'..='\x7e' => {
1521 buf.push(c);
1523 }
1524 '\u{7f}'..='\u{ff}' => {
1525 buf.push(c);
1526 }
1527
1528 _ => {
1529 buf.push(c);
1530 }
1531 }
1532 }
1533
1534 buf
1535}