vue_compiler_core/transformer/
collect_entities.rs

1// this module collects following entities:
2// runtime helpers
3// component/directive asset
4use super::{BaseFor, BaseIf, BaseInfo, BaseRenderSlot, BaseText, BaseVNode, BaseVSlot, CorePass};
5use crate::converter::{BaseRoot, IRNode as IR, JsExpr as Js};
6use crate::flags::{HelperCollector, RuntimeHelper as RH};
7use crate::util::{get_vnode_call_helper, VStr};
8use rustc_hash::FxHashSet;
9use std::mem::swap;
10
11#[derive(Default)]
12pub struct EntityCollector<'a> {
13    helpers: HelperCollector,
14    components: FxHashSet<VStr<'a>>,
15    directives: FxHashSet<VStr<'a>>,
16}
17
18impl<'a> CorePass<BaseInfo<'a>> for EntityCollector<'a> {
19    fn exit_root(&mut self, r: &mut BaseRoot<'a>) {
20        if r.body.len() > 1 {
21            self.helpers.collect(RH::Fragment);
22        }
23        let scope = &mut r.top_scope;
24        swap(&mut scope.helpers, &mut self.helpers);
25        swap(&mut scope.components, &mut self.components);
26        swap(&mut scope.directives, &mut self.directives);
27    }
28    fn exit_js_expr(&mut self, e: &mut Js) {
29        match e {
30            Js::Call(h, ..) | Js::Symbol(h) => {
31                self.helpers.collect(*h);
32            }
33            _ => {}
34        }
35    }
36    fn exit_if(&mut self, i: &mut BaseIf) {
37        if i.branches.iter().all(|b| b.condition.is_some()) {
38            self.helpers.collect(RH::CreateComment);
39        }
40    }
41    fn exit_for(&mut self, f: &mut BaseFor<'a>) {
42        if let IR::AlterableSlot(_) = &*f.child {
43            // v-for in slot only need renderList
44            return self.helpers.collect(RH::RenderList);
45        }
46        self.helpers.collect(RH::OpenBlock);
47        self.helpers.collect(RH::CreateElementBlock);
48        self.helpers.collect(RH::RenderList);
49        self.helpers.collect(RH::Fragment);
50    }
51    fn exit_vnode(&mut self, v: &mut BaseVNode<'a>) {
52        if !v.directives.is_empty() {
53            self.helpers.collect(RH::WithDirectives);
54            // dir with Js::Symbol is collected in js_expr
55            for dir in v.directives.iter() {
56                if let Js::StrLit(d) = dir.name {
57                    self.directives.insert(d);
58                }
59            }
60        }
61        if v.is_block {
62            self.helpers.collect(RH::OpenBlock);
63        }
64        let h = get_vnode_call_helper(v);
65        self.helpers.collect(h);
66        if !v.is_component {
67            return;
68        }
69        // only hoisted asset needs handling, Js::Symbol is collected in js_expr
70        // see [resolve_element_tag] in convert_element
71        if let Some(tag) = is_hoisted_asset(&v.tag) {
72            self.helpers.collect(RH::ResolveComponent);
73            self.components.insert(*tag);
74        }
75        // only StrLit needs handling, see [build_directive_arg] in convert_element
76        let mut hoisted_dir_names = v
77            .directives
78            .iter()
79            .map(|dir| &dir.name)
80            .filter_map(is_hoisted_asset)
81            .peekable();
82        if hoisted_dir_names.peek().is_some() {
83            self.helpers.collect(RH::ResolveDirective);
84        }
85        for dir_name in hoisted_dir_names {
86            self.directives.insert(*dir_name);
87        }
88    }
89    fn exit_slot_outlet(&mut self, _: &mut BaseRenderSlot<'a>) {
90        self.helpers.collect(RH::RenderSlot);
91    }
92    fn exit_v_slot(&mut self, s: &mut BaseVSlot<'a>) {
93        if !s.alterable_slots.is_empty() {
94            self.helpers.collect(RH::CreateSlots);
95        }
96        self.helpers.collect(RH::WithCtx);
97    }
98    fn exit_comment(&mut self, _: &mut &str) {
99        self.helpers.collect(RH::CreateComment);
100    }
101
102    fn exit_text(&mut self, t: &mut BaseText<'a>) {
103        if !t.fast_path {
104            self.helpers.collect(RH::CreateText);
105        }
106    }
107}
108
109pub fn is_hoisted_asset<'a, 'b>(expr: &'b Js<'a>) -> Option<&'b VStr<'a>> {
110    match expr {
111        Js::Simple(n, _) if VStr::is_asset(n) => Some(n),
112        _ => None,
113    }
114}
115
116#[cfg(test)]
117mod test {
118    use super::super::test::{base_convert, get_transformer};
119    use super::*;
120    use crate::transformer::Transformer;
121    fn transform(s: &str) -> BaseRoot {
122        let mut transformer = get_transformer(EntityCollector::default());
123        let mut ir = base_convert(s);
124        transformer.transform(&mut ir);
125        ir
126    }
127    #[test]
128    fn test_v_if_helper() {
129        let ir = transform("<p v-if='a'/>");
130        let helpers = ir.top_scope.helpers;
131        assert!(helpers.contains(RH::CreateComment));
132    }
133    #[test]
134    fn test_v_for_helper() {
135        let ir = transform("<p v-for='a in b'/>");
136        let helpers = ir.top_scope.helpers;
137        assert!(helpers.contains(RH::Fragment));
138        assert!(helpers.contains(RH::OpenBlock));
139        assert!(helpers.contains(RH::RenderList));
140        assert!(!helpers.contains(RH::CreateComment));
141    }
142    #[test]
143    fn test_v_for_alterable_helper() {
144        let ir = transform(
145            "
146        <comp>
147            <template #slot v-for='a in b'/>
148        </comp>",
149        );
150        let helpers = ir.top_scope.helpers;
151        assert!(!helpers.contains(RH::Fragment));
152        assert!(!helpers.contains(RH::CreateElementBlock));
153        assert!(helpers.contains(RH::RenderList));
154        assert!(helpers.contains(RH::WithCtx));
155    }
156}