vue_compiler_core/transformer/
optimize_text.rs

1use 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        // #3756 custom directives can mutate DOM arbitrarily so set no textContent
17        if v.is_component || has_custom_dir(v) {
18            return;
19        }
20        // if this is a plain element with a single text child,
21        // leave it as is since the runtime has dedicated fast path for this
22        // by directly setting textContent of the element
23        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}