vue_compiler_core/transformer/
optimize_text.rs1use smallvec::SmallVec;
2
3use super::{BaseInfo, BaseRenderSlot, BaseSlotFn, BaseVNode, CorePass, IRNode as IR};
4use crate::cast;
5use crate::converter::{BaseIR, BaseRoot, JsExpr as Js};
6
7pub struct TextOptimizer;
8
9impl<'a> CorePass<BaseInfo<'a>> for TextOptimizer {
10 fn enter_root(&mut self, r: &mut BaseRoot<'a>) {
11 merge_consecutive_calls(&mut r.body);
12 optimize_away_call(&mut r.body);
13 }
14 fn enter_vnode(&mut self, v: &mut BaseVNode<'a>) {
15 merge_consecutive_calls(&mut v.children);
16 if v.is_component || has_custom_dir(v) {
18 return;
19 }
20 optimize_away_call(&mut v.children);
24 }
25 fn enter_slot_outlet(&mut self, r: &mut BaseRenderSlot<'a>) {
26 merge_consecutive_calls(&mut r.fallbacks);
27 }
28 fn enter_slot_fn(&mut self, s: &mut BaseSlotFn<'a>) {
29 merge_consecutive_calls(&mut s.body);
30 }
31}
32
33fn merge_consecutive_calls(cs: &mut Vec<BaseIR>) {
34 let mut i = 0;
35 while i < cs.len() {
36 if !matches!(&cs[i], IR::TextCall(_)) {
37 i += 1;
38 continue;
39 }
40 let (left, right) = cs.split_at_mut(i + 1);
41 let dest = must_text(&mut left[i]);
42 let mut j = 0;
43 while j < right.len() {
44 if !matches!(&right[j], IR::TextCall(_)) {
45 break;
46 }
47 let src = must_text(&mut right[j]);
48 dest.extend(src.drain(..));
49 j += 1;
50 }
51 drop(cs.drain(i + 1..i + 1 + j));
52 i += 1;
53 }
54}
55
56fn has_custom_dir(v: &BaseVNode) -> bool {
57 !v.directives.is_empty()
58}
59
60fn optimize_away_call(cs: &mut Vec<BaseIR>) {
61 if cs.len() != 1 {
62 return;
63 }
64 if let IR::TextCall(t) = &mut cs[0] {
65 t.fast_path = true;
66 }
67}
68
69fn must_text<'a, 'b>(a: &'b mut BaseIR<'a>) -> &'b mut SmallVec<[Js<'a>; 1]> {
70 let t = cast!(a, IR::TextCall);
71 &mut t.texts
72}
73
74#[cfg(test)]
75mod test {
76 use super::super::test::{base_convert, get_transformer};
77 use super::super::{BaseText, Transformer};
78 use super::*;
79
80 fn must_ir<'a, 'b>(a: &'b BaseIR<'a>) -> &'b BaseText<'a> {
81 cast!(a, IR::TextCall)
82 }
83
84 #[test]
85 fn test_merge_text() {
86 let mut transformer = get_transformer(TextOptimizer);
87 let mut ir = base_convert("hello {{world}}");
88 assert_eq!(ir.body.len(), 2);
89 assert_eq!(must_text(&mut ir.body[0]).len(), 1);
90 assert_eq!(must_text(&mut ir.body[1]).len(), 1);
91 transformer.transform(&mut ir);
92 assert_eq!(ir.body.len(), 1);
93 assert_eq!(must_text(&mut ir.body[0]).len(), 2);
94 let ir = must_ir(&ir.body[0]);
95 assert!(ir.fast_path);
96 assert!(!ir.need_patch);
97 }
98
99 #[test]
100 fn test_merge_text_with_element() {
101 let mut transformer = get_transformer(TextOptimizer);
102 let mut ir = base_convert("hello <p/> {{world}}");
103 assert_eq!(ir.body.len(), 4);
104 transformer.transform(&mut ir);
105 assert_eq!(ir.body.len(), 3);
106 assert_eq!(must_text(&mut ir.body[2]).len(), 2);
107 assert!(!must_ir(&ir.body[2]).fast_path);
108 let mut ir = base_convert("a <p/> a {{f}} b<p/> e {{c}}<p/>");
109 transformer.transform(&mut ir);
110 assert_eq!(ir.body.len(), 6);
111 }
112 #[test]
113 fn test_merge_text_with_slot() {
114 let mut transformer = get_transformer(TextOptimizer);
115 let mut ir = base_convert("<slot>hello {{world}}</slot>");
116 transformer.transform(&mut ir);
117 assert_eq!(ir.body.len(), 1);
118 let slot = cast!(&mut ir.body[0], IR::RenderSlotCall);
119 assert_eq!(slot.fallbacks.len(), 1);
120 let text = must_text(&mut slot.fallbacks[0]);
121 assert_eq!(text.len(), 2);
122 let ir = must_ir(&slot.fallbacks[0]);
123 assert!(!ir.fast_path);
124 }
125}