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 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 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 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 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 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 if self.in_alterable {
191 return self.generate_render_list(f);
192 }
193 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 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 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 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
284const 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 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 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 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 self.write_str("function render(")?;
373 self.write_str(args)?;
374 self.write_str(") {")?;
375 self.closing_brackets += 1;
376 self.indent()
377 }
378 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 self.gen_helper_destruct(helpers, "_Vue")
393 }
394 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 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 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 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; 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 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#[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
633macro_rules! gen_vnode_args {
639 (
640 $gen:ident,
641 $(
642 $condition: expr, { $($generate: tt)* }
643 )*) => {
644 let mut i = 0;
646 let mut j = 0;
647 $(
648 j += 1;
649 if $condition {
650 i = j;
651 }
652 )*
653 j = -1;
655 $(
656 j += 1;
657 if $condition {
658 if j > 0 {
660 $gen.write_str(", ")?;
661 }
662 $($generate)*
663 } else if i > j {
664 $gen.write_str(", null")?;
666 } else {
667 return Ok(())
668 }
669 )*
670 }
671
672}
673fn 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 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 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}