vue_compiler_core/
codegen.rs

1use crate::converter::RenderSlotIR;
2
3use super::converter::{
4    BaseConvertInfo, BaseIR, BaseRoot, ConvertInfo, IRNode, IRRoot, JsExpr as Js, RuntimeDir,
5    TopScope, VNodeIR,
6};
7use super::flags::{HelperCollector, PatchFlag, RuntimeHelper as RH, SlotFlag};
8use super::transformer::{
9    BaseFor, BaseIf, BaseRenderSlot, BaseSlotFn, BaseText, BaseVNode, BaseVSlot,
10};
11use crate::util::{get_vnode_call_helper, is_simple_identifier, VStr};
12use smallvec::{smallvec, SmallVec};
13use std::marker::PhantomData;
14use std::{
15    borrow::Cow,
16    io::{self, Write},
17    iter,
18};
19
20pub trait CodeGenerator {
21    type IR;
22    type Output;
23    /// generate will take optimized ir node and output
24    /// desired code format, either String or Binary code
25    fn generate(&mut self, node: Self::IR) -> Self::Output;
26}
27
28#[derive(PartialEq, Eq, Clone)]
29pub enum ScriptMode {
30    Function { prefix_identifier: bool },
31    Module,
32}
33
34#[derive(Clone)]
35pub struct CodeGenerateOption {
36    pub is_dev: bool,
37    pub is_ts: bool,
38    pub mode: ScriptMode,
39    pub source_map: bool,
40    pub inline: bool,
41    pub has_binding: bool,
42    // filename for source map
43    pub filename: String,
44    pub decode_entities: EntityDecoder,
45}
46impl CodeGenerateOption {
47    fn use_with_scope(&self) -> bool {
48        match self.mode {
49            ScriptMode::Function { prefix_identifier } => !prefix_identifier,
50            ScriptMode::Module => false,
51        }
52    }
53}
54impl Default for CodeGenerateOption {
55    fn default() -> Self {
56        Self {
57            is_dev: true,
58            is_ts: false,
59            mode: ScriptMode::Function {
60                prefix_identifier: false,
61            },
62            source_map: false,
63            inline: false,
64            filename: String::new(),
65            has_binding: false,
66            decode_entities: |s, _| DecodedStr::from(s),
67        }
68    }
69}
70
71use super::converter as C;
72trait CoreCodeGenerator<T: ConvertInfo>: CodeGenerator<IR = IRRoot<T>> {
73    type Written;
74    fn generate_ir(&mut self, ir: IRNode<T>) -> Self::Written {
75        use IRNode as IR;
76        match ir {
77            IR::TextCall(t) => self.generate_text(t),
78            IR::If(v_if) => self.generate_if(v_if),
79            IR::For(v_for) => self.generate_for(v_for),
80            IR::VNodeCall(vnode) => self.generate_vnode(vnode),
81            IR::RenderSlotCall(r) => self.generate_slot_outlet(r),
82            IR::VSlotUse(s) => self.generate_v_slot(s),
83            IR::CommentCall(c) => self.generate_comment(c),
84            IR::AlterableSlot(a) => self.generate_alterable_slot(a),
85        }
86    }
87    fn generate_prologue(&mut self, t: &IRRoot<T>) -> Self::Written;
88    fn generate_epilogue(&mut self) -> Self::Written;
89    fn generate_text(&mut self, t: C::TextIR<T>) -> Self::Written;
90    fn generate_if(&mut self, i: C::IfNodeIR<T>) -> Self::Written;
91    fn generate_for(&mut self, f: C::ForNodeIR<T>) -> Self::Written;
92    fn generate_vnode(&mut self, v: C::VNodeIR<T>) -> Self::Written;
93    fn generate_slot_outlet(&mut self, r: C::RenderSlotIR<T>) -> Self::Written;
94    fn generate_v_slot(&mut self, s: C::VSlotIR<T>) -> Self::Written;
95    fn generate_alterable_slot(&mut self, s: C::Slot<T>) -> Self::Written;
96    fn generate_js_expr(&mut self, e: T::JsExpression) -> Self::Written;
97    fn generate_comment(&mut self, c: T::CommentType) -> Self::Written;
98}
99
100pub struct CodeWriter<'a, T: Write> {
101    pub writer: T,
102    indent_level: usize,
103    closing_brackets: usize,
104    in_alterable: bool,
105    helpers: HelperCollector,
106    option: CodeGenerateOption,
107    pd: PhantomData<&'a ()>,
108}
109impl<'a, T: Write> CodeWriter<'a, T> {
110    pub fn new(writer: T, option: CodeGenerateOption) -> Self {
111        Self {
112            writer,
113            option,
114            indent_level: 0,
115            closing_brackets: 0,
116            in_alterable: false,
117            helpers: Default::default(),
118            pd: PhantomData,
119        }
120    }
121}
122impl<'a, T: Write> CodeGenerator for CodeWriter<'a, T> {
123    type IR = BaseRoot<'a>;
124    type Output = io::Result<()>;
125    fn generate(&mut self, root: Self::IR) -> Self::Output {
126        // get top scope entities
127        self.helpers = root.top_scope.helpers.clone();
128        self.generate_root(root)
129    }
130}
131
132impl<'a, T: Write> CoreCodeGenerator<BaseConvertInfo<'a>> for CodeWriter<'a, T> {
133    type Written = io::Result<()>;
134    fn generate_prologue(&mut self, root: &BaseRoot<'a>) -> io::Result<()> {
135        self.generate_preamble(&root.top_scope)?;
136        self.generate_function_signature()?;
137        self.generate_with_scope()?;
138        self.generate_assets(&root.top_scope)?;
139        self.write_str("return ")
140    }
141    fn generate_epilogue(&mut self) -> io::Result<()> {
142        for _ in 0..self.closing_brackets {
143            self.deindent(true)?;
144            self.write_str("}")?;
145        }
146        debug_assert_eq!(self.indent_level, 0);
147        Ok(())
148    }
149    fn generate_text(&mut self, t: BaseText<'a>) -> io::Result<()> {
150        if t.fast_path {
151            return self.gen_concate_str(t.texts);
152        }
153        self.write_helper(RH::CreateText)?;
154        self.write_str("(")?;
155        self.gen_concate_str(t.texts)?;
156        if t.need_patch {
157            self.write_str(", ")?;
158            self.write_patch(PatchFlag::TEXT)?;
159        }
160        self.write_str(")")
161    }
162    fn generate_if(&mut self, i: BaseIf<'a>) -> io::Result<()> {
163        let mut indent = 0;
164        for branch in i.branches {
165            if branch.condition.is_none() {
166                // should use into_inner but it's unstable
167                self.generate_ir(*branch.child)?;
168                return self.flush_deindent(indent);
169            }
170            indent += 1;
171            let condition = branch.condition.unwrap();
172            self.write_str("(")?;
173            self.generate_js_expr(condition)?;
174            self.write_str(")")?;
175            self.indent()?;
176            self.write_str("? ")?;
177            self.generate_ir(*branch.child)?;
178            self.newline()?;
179            self.write_str(": ")?;
180        }
181        // TODO: generate undefined for alterable_slots
182        // generate default v-else comment
183        let s = if self.option.is_dev { "'v-if'" } else { "''" };
184        let comment = Js::Call(RH::CreateComment, vec![Js::Src(s), Js::Src("true")]);
185        self.generate_js_expr(comment)?;
186        self.flush_deindent(indent)
187    }
188    fn generate_for(&mut self, f: BaseFor<'a>) -> io::Result<()> {
189        // skip block creation or Fragment in alterable_slots
190        if self.in_alterable {
191            return self.generate_render_list(f);
192        }
193        // write open block
194        self.gen_open_block(f.is_stable, move |gen| {
195            gen.write_helper(RH::CreateElementBlock)?;
196            gen.write_str("(")?;
197            gen_v_for_args(gen, f)?;
198            gen.write_str(")")
199        })
200    }
201    fn generate_vnode(&mut self, v: BaseVNode<'a>) -> io::Result<()> {
202        self.gen_vnode_with_dir(v)
203    }
204    fn generate_slot_outlet(&mut self, r: BaseRenderSlot<'a>) -> io::Result<()> {
205        self.write_helper(RH::RenderSlot)?;
206        self.write_str("(")?;
207        gen_render_slot_args(self, r)?;
208        self.write_str(")")
209    }
210    fn generate_v_slot(&mut self, s: BaseVSlot<'a>) -> io::Result<()> {
211        use Slot::*;
212        let flag = (Js::str_lit("_"), Flag(s.slot_flag));
213        let stable_obj = s
214            .stable_slots
215            .into_iter()
216            .map(|f| (f.name, SlotFn(f.param, f.body)))
217            .chain(iter::once(flag));
218        // no alterable, output object literal. e.g. {default: ... }
219        if s.alterable_slots.is_empty() {
220            return self.gen_obj_props(stable_obj, gen_stable_slot_fn);
221        }
222        self.write_helper(RH::CreateSlots)?;
223        self.write_str("(")?;
224        self.gen_obj_props(stable_obj, gen_stable_slot_fn)?;
225        self.write_str(", ")?;
226        // NB: set in_alterable flag to reuse v-for in slot-fn
227        self.in_alterable = true;
228        self.generate_children(s.alterable_slots)?;
229        debug_assert!(self.in_alterable);
230        self.in_alterable = false;
231        self.write_str(")")
232    }
233    fn generate_js_expr(&mut self, expr: Js<'a>) -> io::Result<()> {
234        match expr {
235            Js::Src(s) | Js::Param(s) => self.write_str(s),
236            Js::Num(n) => write!(self.writer, "{}", n),
237            Js::StrLit(mut l) => l.be_js_str().write_to(&mut self.writer),
238            Js::Simple(e, _) => e.write_to(&mut self.writer),
239            Js::Symbol(s) => self.write_helper(s),
240            Js::Props(p) => self.gen_obj_props(p, |gen, v| gen.generate_js_expr(v)),
241            Js::Compound(v) => {
242                for e in v {
243                    self.generate_js_expr(e)?;
244                }
245                Ok(())
246            }
247            Js::Array(a) => {
248                self.write_str("[")?;
249                self.gen_list(a)?;
250                self.write_str("]")
251            }
252            Js::Call(c, args) => {
253                self.write_helper(c)?;
254                self.write_str("(")?;
255                self.gen_list(args)?;
256                self.write_str(")")
257            }
258        }
259    }
260    fn generate_alterable_slot(&mut self, s: BaseSlotFn<'a>) -> io::Result<()> {
261        debug_assert!(self.in_alterable);
262        // switch back to normal mode
263        self.in_alterable = false;
264        self.write_str("{")?;
265        self.indent()?;
266        self.write_str("name: ")?;
267        self.generate_js_expr(s.name)?;
268        self.write_str(",")?;
269        self.newline()?;
270        self.write_str("fn: ")?;
271        gen_slot_fn(self, (s.param, s.body))?;
272        self.deindent(true)?;
273        self.write_str("}")?;
274        self.in_alterable = true;
275        Ok(())
276    }
277    fn generate_comment(&mut self, c: &'a str) -> io::Result<()> {
278        let comment = Js::str_lit(c);
279        let call = Js::Call(RH::CreateComment, vec![comment]);
280        self.generate_js_expr(call)
281    }
282}
283
284// TODO: put runtimeGlobalName in option
285const RUNTIME_GLOBAL_NAME: &str = "Vue";
286impl<'a, T: Write> CodeWriter<'a, T> {
287    fn generate_root(&mut self, mut root: BaseRoot<'a>) -> io::Result<()> {
288        self.generate_prologue(&root)?;
289        if root.body.is_empty() {
290            self.write_str("null")?;
291        } else {
292            let ir = if root.body.len() == 1 {
293                root.body.pop().unwrap()
294            } else {
295                IRNode::VNodeCall(VNodeIR {
296                    tag: Js::Symbol(RH::Fragment),
297                    children: root.body,
298                    ..VNodeIR::default()
299                })
300            };
301            self.generate_ir(ir)?;
302        }
303        self.generate_epilogue()
304    }
305    /// for import helpers or hoist that not in function
306    fn generate_preamble(&mut self, top: &TopScope<'a>) -> io::Result<()> {
307        if self.option.mode == ScriptMode::Module {
308            self.gen_module_preamble()
309        } else {
310            self.gen_function_preamble(top)
311        }
312    }
313    fn gen_function_preamble(&mut self, top: &TopScope<'a>) -> io::Result<()> {
314        debug_assert!(top.helpers == self.helpers);
315        if !self.helpers.is_empty() {
316            if self.option.use_with_scope() {
317                self.write_str("const _Vue = ")?;
318                self.write_str(RUNTIME_GLOBAL_NAME)?;
319                self.newline()?;
320                // helpers are declared inside with block, but hoists
321                // are lifted out so we need extract hoist helper here.
322                if !top.hoists.is_empty() {
323                    let hoist_helpers = self.helpers.hoist_helpers();
324                    self.gen_helper_destruct(hoist_helpers, RUNTIME_GLOBAL_NAME)?;
325                }
326            } else {
327                let helper = self.helpers.clone();
328                self.gen_helper_destruct(helper, RUNTIME_GLOBAL_NAME)?;
329            }
330        }
331        self.gen_hoist(top)?;
332        self.newline()?;
333        self.write_str("return ")
334    }
335    fn gen_module_preamble(&mut self) -> io::Result<()> {
336        todo!()
337    }
338    fn gen_helper_destruct(&mut self, helpers: HelperCollector, from: &str) -> io::Result<()> {
339        self.write_str("const {")?;
340        self.indent()?;
341        self.gen_helper_import_list(helpers)?;
342        self.deindent(true)?;
343        self.write_str("} = ")?;
344        self.write_str(from)?;
345        self.newline()
346    }
347    fn gen_helper_import_list(&mut self, helpers: HelperCollector) -> io::Result<()> {
348        for rh in helpers.into_iter() {
349            self.write_str(rh.helper_str())?;
350            self.write_str(": _")?;
351            self.write_str(rh.helper_str())?;
352            self.write_str(", ")?;
353        }
354        Ok(())
355    }
356    fn gen_hoist(&mut self, top: &TopScope<'a>) -> io::Result<()> {
357        if top.hoists.is_empty() {
358            return Ok(());
359        }
360        todo!()
361    }
362    /// render() or ssrRender() and their parameters
363    fn generate_function_signature(&mut self) -> io::Result<()> {
364        let option = &self.option;
365        let args = if option.has_binding && !option.inline {
366            "_ctx, _cache, $props, $setup, $data, $options"
367        } else {
368            "_ctx, _cache"
369        };
370        // NB: vue uses arrow func for inline mode.
371        // but it makes no diff in Vue runtime implementation?
372        self.write_str("function render(")?;
373        self.write_str(args)?;
374        self.write_str(") {")?;
375        self.closing_brackets += 1;
376        self.indent()
377    }
378    /// with (ctx) for not prefixIdentifier
379    fn generate_with_scope(&mut self) -> io::Result<()> {
380        let helpers = self.helpers.clone();
381        if !self.option.use_with_scope() {
382            return Ok(());
383        }
384        self.write_str("with (_ctx) {")?;
385        self.closing_brackets += 1;
386        self.indent()?;
387        if helpers.is_empty() {
388            return Ok(());
389        }
390        // function mode const declarations should be inside with block
391        // so it doesn't incur the `in` check cost for every helper access.
392        self.gen_helper_destruct(helpers, "_Vue")
393    }
394    /// component/directive resolution inside render
395    fn generate_assets(&mut self, top: &TopScope<'a>) -> io::Result<()> {
396        if !top.components.is_empty() {
397            self.newline()?;
398            let components = top.components.iter().cloned();
399            gen_assets(self, components, RH::ResolveComponent)?;
400        }
401        if !top.directives.is_empty() {
402            self.newline()?;
403            let directives = top.directives.iter().cloned();
404            gen_assets(self, directives, RH::ResolveDirective)?;
405        }
406        Ok(())
407    }
408
409    fn gen_concate_str(&mut self, t: SmallVec<[Js<'a>; 1]>) -> io::Result<()> {
410        let mut texts = t.into_iter();
411        match texts.next() {
412            Some(t) => self.generate_js_expr(t)?,
413            None => return Ok(()),
414        }
415        for t in texts {
416            self.write_str(" + ")?;
417            self.generate_js_expr(t)?;
418        }
419        Ok(())
420    }
421
422    fn generate_children(&mut self, children: Vec<BaseIR<'a>>) -> io::Result<()> {
423        debug_assert!(!children.is_empty());
424        let fast = if let IRNode::TextCall(t) = &children[0] {
425            t.fast_path
426        } else {
427            false
428        };
429        if fast {
430            // generate sole text node without []
431            let ir = children.into_iter().next().unwrap();
432            return self.generate_ir(ir);
433        }
434        self.write_str("[")?;
435        self.indent()?;
436        for child in children {
437            self.generate_ir(child)?;
438            self.write_str(", ")?;
439        }
440        self.deindent(true)?;
441        self.write_str("]")
442    }
443    fn generate_render_list(&mut self, f: BaseFor<'a>) -> io::Result<()> {
444        self.write_helper(RH::RenderList)?;
445        self.write_str("(")?;
446        self.generate_js_expr(f.source)?;
447        self.write_str(", ")?;
448        let p = f.parse_result;
449        let params = vec![Some(p.value), p.key, p.index];
450        self.gen_func_expr(params, *f.child)?;
451        self.write_str(")")
452    }
453    // TODO: add newline
454    fn gen_func_expr(&mut self, params: Vec<Option<Js<'a>>>, body: BaseIR<'a>) -> io::Result<()> {
455        const PLACE_HOLDER: &[&str] = &[
456            "_", "_1", "_2", "_3", "_4", "_5", "_6", "_7", "_8", "_9", "_0",
457        ];
458        let last = params
459            .iter()
460            .rposition(Option::is_some)
461            .map(|i| i + 1)
462            .unwrap_or(0);
463        debug_assert!(
464            last < PLACE_HOLDER.len(),
465            "Too many params to generate placeholder"
466        );
467        let normalized_params = params
468            .into_iter()
469            .take(last)
470            .enumerate()
471            .map(|(i, o)| o.unwrap_or(Js::Src(PLACE_HOLDER[i])));
472        self.write_str("(")?;
473        self.gen_list(normalized_params)?;
474        self.write_str(") => {")?;
475        self.indent()?;
476        self.write_str("return ")?;
477        self.generate_ir(body)?;
478        self.deindent(true)?;
479        self.write_str("}")
480    }
481    /// generate a comma separated list
482    fn gen_list<I>(&mut self, exprs: I) -> io::Result<()>
483    where
484        I: IntoIterator<Item = Js<'a>>,
485    {
486        let mut exprs = exprs.into_iter();
487        if let Some(e) = exprs.next() {
488            self.generate_js_expr(e)?;
489        } else {
490            return Ok(());
491        }
492        for e in exprs {
493            self.write_str(", ")?;
494            self.generate_js_expr(e)?;
495        }
496        Ok(())
497    }
498    fn gen_obj_props<V, P, K>(&mut self, props: P, cont: K) -> io::Result<()>
499    where
500        P: IntoIterator<Item = (Js<'a>, V)>,
501        K: Fn(&mut Self, V) -> io::Result<()>,
502    {
503        let mut props = props.into_iter().peekable();
504        if props.peek().is_none() {
505            return self.write_str("{}");
506        }
507        self.write_str("{")?;
508        self.indent_level += 1; // don't call newline
509        for (key, val) in props {
510            self.newline()?;
511            self.gen_obj_key(key)?;
512            self.write_str(": ")?;
513            cont(self, val)?;
514            self.write_str(",")?;
515        }
516        self.deindent(true)?;
517        self.write_str("}")
518    }
519    fn gen_obj_key(&mut self, key: Js<'a>) -> io::Result<()> {
520        if let Js::StrLit(mut k) = key {
521            if is_simple_identifier(k) {
522                k.write_to(&mut self.writer)
523            } else {
524                k.be_js_str().write_to(&mut self.writer)
525            }
526        } else {
527            self.write_str("[")?;
528            self.generate_js_expr(key)?;
529            self.write_str("]")
530        }
531    }
532    fn gen_vnode_with_dir(&mut self, mut v: BaseVNode<'a>) -> io::Result<()> {
533        if v.directives.is_empty() {
534            return self.gen_vnode_with_block(v);
535        }
536        let dirs = std::mem::take(&mut v.directives);
537        self.write_helper(RH::WithDirectives)?;
538        self.write_str("(")?;
539        self.gen_vnode_with_block(v)?;
540        self.write_str(", ")?;
541        let dir_arr = runtime_dirs_to_js_arr(dirs);
542        self.generate_js_expr(dir_arr)?;
543        self.write_str(")")
544    }
545    fn gen_vnode_with_block(&mut self, v: BaseVNode<'a>) -> io::Result<()> {
546        if !v.is_block {
547            return gen_vnode_real(self, v);
548        }
549        self.gen_open_block(v.disable_tracking, move |gen| gen_vnode_real(gen, v))
550    }
551    fn gen_open_block<K>(&mut self, no_track: bool, cont: K) -> io::Result<()>
552    where
553        K: FnOnce(&mut Self) -> io::Result<()>,
554    {
555        self.write_str("(")?;
556        self.write_helper(RH::OpenBlock)?;
557        self.write_str("(")?;
558        if no_track {
559            self.write_str("true")?;
560        }
561        self.write_str("), ")?;
562        cont(self)?;
563        self.write_str(")")
564    }
565
566    fn newline(&mut self) -> io::Result<()> {
567        self.write_str("\n")?;
568        // TODO: use exponential adding + lazy static
569        for _ in 0..self.indent_level {
570            self.write_str("  ")?;
571        }
572        Ok(())
573    }
574    fn indent(&mut self) -> io::Result<()> {
575        self.indent_level += 1;
576        self.newline()
577    }
578    fn deindent(&mut self, with_new_line: bool) -> io::Result<()> {
579        debug_assert!(self.indent_level > 0);
580        self.indent_level -= 1;
581        if with_new_line {
582            self.newline()
583        } else {
584            Ok(())
585        }
586    }
587    fn flush_deindent(&mut self, mut indent: usize) -> io::Result<()> {
588        while indent > 0 {
589            self.deindent(false)?;
590            indent -= 1;
591        }
592        Ok(())
593    }
594
595    #[inline(always)]
596    fn write_str(&mut self, s: &str) -> io::Result<()> {
597        self.writer.write_all(s.as_bytes())
598    }
599    #[inline(always)]
600    fn write_helper(&mut self, h: RH) -> io::Result<()> {
601        debug_assert!(self.helpers.contains(h));
602        self.write_str("_")?;
603        self.write_str(h.helper_str())
604    }
605    #[inline(always)]
606    fn write_patch(&mut self, flag: PatchFlag) -> io::Result<()> {
607        write!(self.writer, "{} /*{:?}*/", flag.bits(), flag)
608    }
609}
610
611/// DecodedStr represents text after decoding html entities.
612/// SmallVec and Cow are used internally for less allocation.
613#[derive(Debug)]
614pub struct DecodedStr<'a>(SmallVec<[Cow<'a, str>; 1]>);
615
616impl<'a> From<&'a str> for DecodedStr<'a> {
617    fn from(decoded: &'a str) -> Self {
618        debug_assert!(!decoded.is_empty());
619        Self(smallvec![Cow::Borrowed(decoded)])
620    }
621}
622
623pub type EntityDecoder = fn(&str, bool) -> DecodedStr<'_>;
624
625fn gen_vnode_real<'a, T: Write>(gen: &mut CodeWriter<'a, T>, v: BaseVNode<'a>) -> io::Result<()> {
626    let call_helper = get_vnode_call_helper(&v);
627    gen.write_helper(call_helper)?;
628    gen.write_str("(")?;
629    gen_vnode_call_args(gen, v)?;
630    gen.write_str(")")
631}
632
633// no, repeating myself is good. macro is bad
634/// Takes generator and, condition/generation code pairs.
635/// It first finds the last index to write.
636/// then generate code for each arg, filling null if empty
637/// util the last index to write is reached.
638macro_rules! gen_vnode_args {
639    (
640    $gen:ident,
641    $(
642        $condition: expr, { $($generate: tt)* }
643    )*) => {
644        // 1. find the last index to write
645        let mut i = 0;
646        let mut j = 0;
647        $(
648            j += 1;
649            if $condition {
650                i = j;
651            }
652        )*
653        // 2. write code
654        j = -1;
655        $(
656            j += 1;
657            if $condition {
658                // write comma separator
659                if j > 0 {
660                    $gen.write_str(", ")?;
661                }
662                $($generate)*
663            } else if i > j {
664                // fill null, add comma since first condition must be true
665                $gen.write_str(", null")?;
666            } else {
667                return Ok(())
668            }
669        )*
670    }
671
672}
673// TODO: unit test this monster
674/// Generate variadic vnode call argument list separated by comma.
675/// VNode arg is a heterogeneous list we need hard code the generation.
676fn gen_vnode_call_args<'a, T: Write>(
677    gen: &mut CodeWriter<'a, T>,
678    v: BaseVNode<'a>,
679) -> io::Result<()> {
680    let VNodeIR {
681        tag,
682        props,
683        children,
684        patch_flag,
685        dynamic_props,
686        ..
687    } = v;
688
689    gen_vnode_args!(
690        gen,
691        true, { gen.generate_js_expr(tag)?; }
692        props.is_some(), { gen.generate_js_expr(props.unwrap())?; }
693        !children.is_empty(), { gen.generate_children(children)?; }
694        patch_flag != PatchFlag::empty(), {
695            gen.write_patch(patch_flag)?;
696        }
697        !dynamic_props.is_empty(), {
698            let dps = dynamic_props.into_iter().map(Js::StrLit);
699            gen.write_str("[")?;
700            gen.gen_list(dps)?;
701            gen.write_str("]")?;
702        }
703    );
704    Ok(())
705}
706
707fn gen_v_for_args<'a, T: Write>(gen: &mut CodeWriter<'a, T>, f: BaseFor<'a>) -> io::Result<()> {
708    let flag = f.fragment_flag;
709    gen_vnode_args!(
710        gen,
711        true, { gen.write_helper(RH::Fragment)?; }
712        false, {  }
713        true, { gen.generate_render_list(f)?; }
714        true, {
715            write!(gen.writer, "{} /*{:?}*/", flag.bits(), flag)?;
716        }
717    );
718    Ok(())
719}
720
721fn gen_render_slot_args<'a, T: Write>(
722    gen: &mut CodeWriter<'a, T>,
723    r: BaseRenderSlot<'a>,
724) -> io::Result<()> {
725    let RenderSlotIR {
726        slot_obj,
727        slot_name,
728        slot_props,
729        fallbacks,
730        no_slotted,
731    } = r;
732    gen.generate_js_expr(slot_obj)?;
733    gen.write_str(", ")?;
734    gen.generate_js_expr(slot_name)?;
735    if let Some(prop) = slot_props {
736        gen.write_str(", ")?;
737        gen.generate_js_expr(prop)?;
738    } else {
739        debug_assert!(fallbacks.is_empty() && !no_slotted);
740        return Ok(());
741    }
742    if !fallbacks.is_empty() {
743        gen.write_str(", ")?;
744        gen.write_str("() => ")?;
745        gen.generate_children(fallbacks)?;
746    } else if no_slotted {
747        gen.write_str(", ")?;
748        gen.write_str("undefined")?;
749    }
750    if no_slotted {
751        gen.write_str(", ")?;
752        gen.write_str("true")
753    } else {
754        Ok(())
755    }
756}
757
758enum Slot<'a> {
759    SlotFn(Option<Js<'a>>, Vec<BaseIR<'a>>),
760    Flag(SlotFlag),
761}
762fn gen_stable_slot_fn<'a, T: Write>(gen: &mut CodeWriter<'a, T>, slot: Slot<'a>) -> io::Result<()> {
763    match slot {
764        Slot::SlotFn(param, body) => gen_slot_fn(gen, (param, body)),
765        Slot::Flag(flag) => {
766            write!(gen.writer, "{} /*{:?}*/", flag as u8, flag)
767        }
768    }
769}
770fn gen_slot_fn<'a, T: Write>(
771    gen: &mut CodeWriter<'a, T>,
772    (param, body): (Option<Js<'a>>, Vec<BaseIR<'a>>),
773) -> io::Result<()> {
774    gen.write_helper(RH::WithCtx)?;
775    gen.write_str("(")?;
776    gen.write_str("(")?;
777    if let Some(p) = param {
778        gen.generate_js_expr(p)?;
779    }
780    gen.write_str(") => [")?;
781    gen.indent()?;
782    let mut body = body.into_iter();
783    if let Some(b) = body.next() {
784        gen.generate_ir(b)?;
785    }
786    for b in body {
787        gen.write_str(", ")?;
788        gen.newline()?;
789        gen.generate_ir(b)?;
790    }
791    gen.deindent(true)?;
792    gen.write_str("]")?;
793    gen.write_str(")")
794}
795fn gen_assets<'a, T: Write>(
796    gen: &mut CodeWriter<'a, T>,
797    assets: impl Iterator<Item = VStr<'a>>,
798    resolver: RH,
799) -> io::Result<()> {
800    for asset in assets {
801        let hint = if VStr::is_self_suffixed(&asset) {
802            ", true"
803        } else {
804            ""
805        };
806        gen.write_str("const ")?;
807        asset.write_to(&mut gen.writer)?;
808        gen.write_str(" = ")?;
809        gen.write_helper(resolver)?;
810        gen.write_str("(")?;
811        let raw = if resolver == RH::ResolveComponent {
812            *asset.clone().unbe_component()
813        } else {
814            *asset.clone().unbe_directive()
815        };
816        raw.write_to(&mut gen.writer)?;
817        gen.write_str(hint)?;
818        gen.write_str(")")?;
819        gen.newline()?;
820    }
821    Ok(())
822}
823
824fn runtime_dir(dir: RuntimeDir<BaseConvertInfo>) -> Js {
825    let arr = vec![Some(dir.name), dir.expr, dir.arg, dir.mods];
826    let last = arr
827        .iter()
828        .rposition(Option::is_some)
829        .map(|i| i + 1)
830        .unwrap_or(0);
831    let arr = arr
832        .into_iter()
833        .take(last)
834        .map(|o| o.unwrap_or(Js::Src("void 0")))
835        .collect();
836    Js::Array(arr)
837}
838
839fn runtime_dirs_to_js_arr(dirs: Vec<RuntimeDir<BaseConvertInfo>>) -> Js {
840    let dirs = dirs.into_iter().map(runtime_dir).collect();
841    Js::Array(dirs)
842}
843
844#[cfg(test)]
845mod test {
846    use super::super::converter::test::base_convert;
847    use super::*;
848    use crate::cast;
849    fn gen(mut ir: BaseRoot, option: CodeGenerateOption) -> String {
850        ir.top_scope.helpers.ignore_missing();
851        let mut writer = CodeWriter::new(vec![], option);
852        writer.generate(ir).unwrap();
853        String::from_utf8(writer.writer).unwrap()
854    }
855    fn base_gen(s: &str) -> String {
856        let ir = base_convert(s);
857        let option = CodeGenerateOption::default();
858        gen(ir, option)
859    }
860    #[test]
861    fn test_text() {
862        let s = base_gen("hello       world");
863        assert!(s.contains(stringify!("hello world")));
864        let s = base_gen("hello {{world}}");
865        assert!(s.contains(stringify!("hello ")));
866        assert!(s.contains("_createTextVNode(_toDisplayString(world))"));
867    }
868    #[test]
869    fn test_text_merge() {
870        let mut ir = base_convert("hello{{world}}");
871        let world = ir.body.pop().unwrap();
872        let world = cast!(world, IRNode::TextCall);
873        let hello = cast!(&mut ir.body[0], IRNode::TextCall);
874        hello.texts.extend(world.texts);
875        let s = gen(ir, CodeGenerateOption::default());
876        assert!(s.contains("\"hello\" + _toDisplayString(world)"), "{}", s);
877    }
878    #[test]
879    fn test_text_fast_path() {
880        let mut ir = base_convert("hello");
881        let hello = cast!(&mut ir.body[0], IRNode::TextCall);
882        hello.fast_path = true;
883        let s = gen(ir, CodeGenerateOption::default());
884        assert!(!s.contains("_createTextVNode"), "{}", s);
885    }
886    #[test]
887    fn test_v_element() {
888        let s = base_gen("<p></p>");
889        assert!(s.contains("\"p\""), "{}", s);
890        assert!(s.contains("createElementVNode"), "{}", s);
891    }
892    #[test]
893    fn test_self_closing() {
894        let s = base_gen("<p/>");
895        assert!(s.contains("\"p\""), "{}", s);
896        assert!(s.contains("createElementVNode"), "{}", s);
897        let mut ir = base_convert("<p/>");
898        let vn = cast!(&mut ir.body[0], IRNode::VNodeCall);
899        vn.is_block = true;
900        let s = gen(ir, CodeGenerateOption::default());
901        assert!(s.contains("openBlock"), "{}", s);
902    }
903    #[test]
904    fn test_attr() {
905        let s = base_gen("<p class='test' id='id'/>");
906        assert!(s.contains("\"p\""), "{}", s);
907        assert!(s.contains(r#"class: "test""#), "{}", s);
908        assert!(s.contains(r#"id: "id""#), "{}", s);
909        let s = base_gen("<button aria-label='close'/>");
910        assert!(s.contains(r#""aria-label": "close""#), "{}", s);
911    }
912    #[test]
913    fn test_v_bind_shorthand() {
914        let s = base_gen("<p :prop='id'/>");
915        assert!(s.contains("prop: id"), "{}", s);
916        let s = base_gen("<p :a='a' :b='b' />");
917        assert!(s.contains("a: a,"), "{}", s);
918        assert!(s.contains("b: b,"), "{}", s);
919        assert!(s.contains("PROPS"), "{}", s);
920        let s = base_gen("<p :prop />");
921        assert!(s.contains(r#"prop: """#), "{}", s);
922    }
923    #[test]
924    fn test_v_bind_dir() {
925        let s = base_gen("<p v-bind:prop='id'/>");
926        assert!(s.contains("prop: id"), "{}", s);
927        let s = base_gen("<p v-bind=prop />");
928        // the below is only in the dom build
929        // assert!(s.contains("_normalizeProps(_guardReactiveProps(prop))"), "{}", s);
930        assert!(s.contains(", prop, null,"), "{}", s);
931        assert!(s.contains("FULL_PROPS"), "{}", s);
932        let s = base_gen("<p v-bind=prop class=test />");
933        assert!(s.contains("_mergeProps(prop"), "{}", s);
934        assert!(s.contains(r#"class: "test""#), "{}", s);
935        assert!(s.contains("FULL_PROPS"), "{}", s);
936    }
937
938    #[test]
939    fn test_v_if() {
940        let s = base_gen("<p v-if='condition'/>");
941        assert!(s.contains("\"p\""), "{}", s);
942        assert!(s.contains("condition"), "{}", s);
943        assert!(s.contains("? "), "{}", s);
944        assert!(s.contains("createCommentVNode"), "{}", s);
945        let mut ir = base_convert("<p v-if='condition'/>");
946        let i = cast!(&mut ir.body[0], IRNode::If);
947        let vn = cast!(&mut *i.branches[0].child, IRNode::VNodeCall);
948        vn.is_block = true;
949        let s = gen(ir, CodeGenerateOption::default());
950        assert!(s.contains("openBlock"), "{}", s);
951    }
952    #[test]
953    fn test_v_if_slot() {
954        let s = base_gen("<slot v-if='condition'/>");
955        assert!(!s.contains("openBlock"), "{}", s);
956        assert!(s.contains("? "), "{}", s);
957        assert!(s.contains("createCommentVNode"), "{}", s);
958    }
959
960    #[test]
961    fn test_v_for() {
962        let s = base_gen("<p v-for='a in b'/>");
963        assert!(s.contains("\"p\""), "{}", s);
964        assert!(s.contains("(a) =>"), "{}", s);
965        assert!(s.contains("_createElementBlock"), "{}", s);
966        let s = base_gen("<p v-for='(a, b, c) in d'/>");
967        assert!(s.contains("\"p\""), "{}", s);
968        assert!(s.contains("(a, b, c) =>"), "{}", s);
969    }
970    #[test]
971    fn test_slot_outlet() {
972        let s = base_gen("<slot name=test />");
973        assert!(s.contains("_renderSlot"), "{}", s);
974        assert!(s.contains(r#", "test""#), "{}", s);
975        let s = base_gen("<slot :name=test />");
976        assert!(s.contains(", test"), "{}", s);
977        let s = base_gen("<slot>fallback</slot>");
978        assert!(s.contains("() => ["), "{}", s);
979        assert!(s.contains(r#""fallback""#), "{}", s);
980    }
981    #[test]
982    fn test_size() {
983        let ir_size = std::mem::size_of::<BaseIR<'_>>();
984        let vnode_size = std::mem::size_of::<BaseVNode<'_>>();
985        let for_size = std::mem::size_of::<BaseFor<'_>>();
986        let js_size = std::mem::size_of::<Js<'_>>();
987        let set_size = std::mem::size_of::<std::collections::HashSet<&str>>();
988        // TODO: too large
989        assert_eq!(ir_size, 184);
990        assert_eq!(vnode_size, 152);
991        assert_eq!(for_size, 176);
992        assert_eq!(js_size, 32);
993        assert_eq!(set_size, 48);
994    }
995    #[test]
996    fn test_implicit_slot() {
997        let s = base_gen("<component is='test'>test</component>");
998        assert!(s.contains("_withCtx"), "{}", s);
999    }
1000
1001    #[test]
1002    fn test_render_func_args() {
1003        let option = CodeGenerateOption {
1004            has_binding: true,
1005            ..Default::default()
1006        };
1007        let ir = base_convert("hello world");
1008        let s = gen(ir, option);
1009        assert!(s.contains("$data"), "{}", s);
1010        let s = base_gen("hello world");
1011        assert!(!s.contains("$setup"), "{}", s);
1012    }
1013}