vize_atelier_ssr 0.258.0

Vue SSR compiler for Vize
Documentation
//! Slot outlet SSR emission and slot prop collection.

use super::props::{
    component_prop_entry, component_props_object, normalize_prop_entries, quoted_js_string,
    static_slot_outlet_prop_key, transform_slot_outlet_bound_prop_key,
};
use super::{
    ElementNode, ExpressionNode, PropNode, RuntimeHelper, SsrCodegenContext, String,
    ToCompactString, VNodePropEntry,
};

impl<'a> SsrCodegenContext<'a> {
    /// Process a slot outlet (<slot>)
    pub(super) fn process_slot_outlet(&mut self, el: &ElementNode<'a>) {
        self.flush_push();
        self.use_ssr_helper(RuntimeHelper::SsrRenderSlot);

        self.push_indent();
        self.push("_ssrRenderSlot(_ctx.$slots, ");

        // Get slot name
        let slot_name = self.slot_outlet_name_expression(el);
        self.push(&slot_name);
        self.push(", ");

        // Slot props
        let props = self.build_slot_outlet_props(el);
        self.push(&props);
        self.push(", ");

        // Fallback content
        if el.children.is_empty() {
            self.push("null");
        } else {
            self.push("() => {\n");
            self.indent_level += 1;

            let old_parts = std::mem::take(&mut self.current_template_parts);
            self.process_children(&el.children, false, false, false);
            self.flush_push();
            self.current_template_parts = old_parts;

            self.indent_level -= 1;
            self.push_indent();
            self.push("}");
        }

        self.push(", _push, _parent");

        // Scope ID
        if self.options.scope_id.is_some() {
            self.push(", _scopeId");
        }

        self.push(")\n");
    }

    pub(super) fn build_slot_outlet_props(&mut self, el: &ElementNode) -> String {
        let mut entries: std::vec::Vec<VNodePropEntry> = std::vec::Vec::new();
        let mut spreads: std::vec::Vec<String> = std::vec::Vec::new();

        for prop in &el.props {
            match prop {
                PropNode::Attribute(attr) => {
                    if attr.name == "name" {
                        continue;
                    }
                    let value = attr
                        .value
                        .as_ref()
                        .map(|v| quoted_js_string(&v.content))
                        .unwrap_or_else(|| "true".to_compact_string());
                    let key = static_slot_outlet_prop_key(&attr.name);
                    entries.push(component_prop_entry(&key, &value, false));
                }
                PropNode::Directive(dir) if dir.name == "bind" => {
                    let value = dir
                        .exp
                        .as_ref()
                        .map(|exp| self.expression_to_string(exp))
                        .unwrap_or_else(|| "undefined".to_compact_string());

                    let Some(arg) = &dir.arg else {
                        spreads.push(value);
                        continue;
                    };

                    let ExpressionNode::Simple(arg) = arg else {
                        entries.push(component_prop_entry(
                            &self.expression_to_string(arg),
                            &value,
                            true,
                        ));
                        continue;
                    };

                    if arg.is_static && arg.content == "name" {
                        continue;
                    }

                    let key = if arg.is_static {
                        transform_slot_outlet_bound_prop_key(&arg.content, dir)
                    } else {
                        arg.content.clone()
                    };
                    entries.push(component_prop_entry(&key, &value, !arg.is_static));
                }
                _ => {}
            }
        }

        let entries = normalize_prop_entries(entries);
        let object = if entries.is_empty() {
            "{}".to_compact_string()
        } else {
            component_props_object(&entries)
        };

        if spreads.is_empty() {
            return object;
        }

        self.use_core_helper(RuntimeHelper::MergeProps);
        let mut args = spreads;
        if !entries.is_empty() {
            args.push(object);
        }

        let mut out = String::from("_mergeProps(");
        for (index, arg) in args.iter().enumerate() {
            if index > 0 {
                out.push_str(", ");
            }
            out.push_str(arg);
        }
        out.push(')');
        out
    }

    /// Get the JavaScript expression for a slot outlet name.
    pub(super) fn slot_outlet_name_expression(&mut self, el: &ElementNode) -> String {
        use vize_atelier_core::{ExpressionNode, PropNode};

        for prop in &el.props {
            if let PropNode::Directive(dir) = prop {
                if dir.name == "bind"
                    && let Some(ExpressionNode::Simple(arg)) = &dir.arg
                    && arg.content == "name"
                {
                    return dir
                        .exp
                        .as_ref()
                        .map(|exp| self.expression_to_string(exp))
                        .unwrap_or_else(|| quoted_js_string("default"));
                }
            } else if let PropNode::Attribute(attr) = prop
                && attr.name == "name"
                && let Some(value) = &attr.value
            {
                return quoted_js_string(&value.content);
            }
        }
        quoted_js_string("default")
    }
}