use crate::ast::{
DynamicProps, ExpressionNode, JsChildNode, PropsExpression, RootNode, RuntimeHelper,
TemplateTextChildNode, VNodeCall, VNodeChildren, VNodeTag,
};
use super::{context::CodegenContext, helpers::escape_js_string};
use vize_carton::String;
use vize_carton::ToCompactString;
pub(super) fn generate_hoists(ctx: &CodegenContext, root: &RootNode<'_>) -> String {
let mut hoists_code: Vec<u8> = Vec::new();
for (i, hoist) in root.hoists.iter().enumerate() {
if let Some(node) = hoist {
hoists_code.extend_from_slice(b"const _hoisted_");
hoists_code.extend_from_slice((i + 1).to_compact_string().as_bytes());
hoists_code.extend_from_slice(b" = ");
if matches!(node, JsChildNode::VNodeCall(_)) {
hoists_code.extend_from_slice(b"/*#__PURE__*/ ");
}
generate_js_child_node_to_bytes(ctx, node, &mut hoists_code);
hoists_code.push(b'\n');
}
}
unsafe { String::from_utf8_unchecked(hoists_code) }
}
pub(super) fn collect_hoist_helpers(root: &RootNode<'_>, helpers: &mut Vec<RuntimeHelper>) {
for node in root.hoists.iter().flatten() {
collect_helpers_from_js_child_node(node, helpers);
}
}
fn collect_helpers_from_js_child_node(node: &JsChildNode<'_>, helpers: &mut Vec<RuntimeHelper>) {
match node {
JsChildNode::VNodeCall(vnode) => collect_helpers_from_vnode_call(vnode, helpers),
JsChildNode::Object(obj) => {
for prop in &obj.properties {
collect_helpers_from_js_child_node(&prop.value, helpers);
}
}
_ => {}
}
}
fn collect_helpers_from_vnode_call(vnode: &VNodeCall<'_>, helpers: &mut Vec<RuntimeHelper>) {
if vnode.is_block {
helpers.push(RuntimeHelper::OpenBlock);
if vnode.is_component {
helpers.push(RuntimeHelper::CreateBlock);
} else {
helpers.push(RuntimeHelper::CreateElementBlock);
}
} else if vnode.is_component {
helpers.push(RuntimeHelper::CreateVNode);
} else {
helpers.push(RuntimeHelper::CreateElementVNode);
}
if let VNodeTag::Symbol(helper) = &vnode.tag {
helpers.push(*helper);
}
if let Some(props) = &vnode.props {
collect_helpers_from_props(props, helpers);
}
}
fn collect_helpers_from_props(props: &PropsExpression<'_>, helpers: &mut Vec<RuntimeHelper>) {
if let PropsExpression::Object(obj) = props {
for prop in &obj.properties {
collect_helpers_from_js_child_node(&prop.value, helpers);
}
}
}
fn generate_js_child_node_to_bytes(
ctx: &CodegenContext,
node: &JsChildNode<'_>,
out: &mut Vec<u8>,
) {
match node {
JsChildNode::VNodeCall(vnode) => generate_vnode_call_to_bytes(ctx, vnode, out),
JsChildNode::SimpleExpression(exp) => {
if exp.is_static {
out.push(b'"');
let escaped = escape_js_string(&exp.content);
out.extend_from_slice(escaped.as_bytes());
out.push(b'"');
} else {
out.extend_from_slice(exp.content.as_bytes());
}
}
JsChildNode::Object(obj) => {
out.extend_from_slice(b"{ ");
for (i, prop) in obj.properties.iter().enumerate() {
if i > 0 {
out.extend_from_slice(b", ");
}
match &prop.key {
ExpressionNode::Simple(exp) => {
let key = &exp.content;
let needs_quote = !crate::codegen::helpers::is_valid_js_identifier(key);
if needs_quote {
out.push(b'"');
out.extend_from_slice(key.as_bytes());
out.push(b'"');
} else {
out.extend_from_slice(key.as_bytes());
}
out.extend_from_slice(b": ");
}
ExpressionNode::Compound(_) => out.extend_from_slice(b"null: "),
}
generate_js_child_node_to_bytes(ctx, &prop.value, out);
}
out.extend_from_slice(b" }");
}
_ => out.extend_from_slice(b"null /* unsupported */"),
}
}
fn generate_vnode_call_to_bytes(ctx: &CodegenContext, vnode: &VNodeCall<'_>, out: &mut Vec<u8>) {
if vnode.is_block {
out.push(b'(');
out.extend_from_slice(ctx.helper(RuntimeHelper::OpenBlock).as_bytes());
out.extend_from_slice(b"(), ");
if vnode.is_component {
out.extend_from_slice(ctx.helper(RuntimeHelper::CreateBlock).as_bytes());
} else {
out.extend_from_slice(ctx.helper(RuntimeHelper::CreateElementBlock).as_bytes());
}
} else if vnode.is_component {
out.extend_from_slice(ctx.helper(RuntimeHelper::CreateVNode).as_bytes());
} else {
out.extend_from_slice(ctx.helper(RuntimeHelper::CreateElementVNode).as_bytes());
}
out.push(b'(');
match &vnode.tag {
VNodeTag::String(s) => {
out.push(b'"');
out.extend_from_slice(s.as_bytes());
out.push(b'"');
}
VNodeTag::Symbol(helper) => out.extend_from_slice(ctx.helper(*helper).as_bytes()),
VNodeTag::Call(_) => out.extend_from_slice(b"null"),
}
if let Some(props) = &vnode.props {
out.extend_from_slice(b", ");
generate_props_expression_to_bytes(ctx, props, out);
} else if vnode.children.is_some() || vnode.patch_flag.is_some() {
out.extend_from_slice(b", null");
}
if let Some(children) = &vnode.children {
out.extend_from_slice(b", ");
generate_vnode_children_to_bytes(ctx, children, out);
} else if vnode.patch_flag.is_some() {
out.extend_from_slice(b", null");
}
if let Some(patch_flag) = &vnode.patch_flag {
out.extend_from_slice(b", ");
out.extend_from_slice(patch_flag.bits().to_compact_string().as_bytes());
out.extend_from_slice(b" /* ");
let mut debug = String::default();
use std::fmt::Write as _;
let _ = write!(&mut debug, "{:?}", patch_flag);
out.extend_from_slice(debug.as_bytes());
out.extend_from_slice(b" */");
}
if let Some(dynamic_props) = &vnode.dynamic_props {
out.extend_from_slice(b", ");
match dynamic_props {
DynamicProps::String(s) => {
out.extend_from_slice(s.as_bytes());
}
DynamicProps::Simple(exp) => {
out.extend_from_slice(exp.content.as_bytes());
}
}
}
out.push(b')');
if vnode.is_block {
out.push(b')');
}
}
fn generate_props_expression_to_bytes(
ctx: &CodegenContext,
props: &PropsExpression<'_>,
out: &mut Vec<u8>,
) {
match props {
PropsExpression::Object(obj) => {
out.extend_from_slice(b"{ ");
for (i, prop) in obj.properties.iter().enumerate() {
if i > 0 {
out.extend_from_slice(b", ");
}
match &prop.key {
ExpressionNode::Simple(exp) => {
let key = &exp.content;
let needs_quote = !crate::codegen::helpers::is_valid_js_identifier(key);
if needs_quote {
out.push(b'"');
out.extend_from_slice(key.as_bytes());
out.push(b'"');
} else {
out.extend_from_slice(key.as_bytes());
}
out.extend_from_slice(b": ");
}
ExpressionNode::Compound(_) => out.extend_from_slice(b"null: "),
}
generate_js_child_node_to_bytes(ctx, &prop.value, out);
}
out.extend_from_slice(b" }");
}
PropsExpression::Simple(exp) => {
if exp.is_static {
out.push(b'"');
out.extend_from_slice(exp.content.as_bytes());
out.push(b'"');
} else {
out.extend_from_slice(exp.content.as_bytes());
}
}
PropsExpression::Call(_) => out.extend_from_slice(b"null"),
}
}
fn generate_vnode_children_to_bytes(
_ctx: &CodegenContext,
children: &VNodeChildren<'_>,
out: &mut Vec<u8>,
) {
match children {
VNodeChildren::Single(text_child) => match text_child {
TemplateTextChildNode::Text(text) => {
out.push(b'"');
out.extend_from_slice(escape_js_string(&text.content).as_bytes());
out.push(b'"');
}
TemplateTextChildNode::Interpolation(_) => out.extend_from_slice(b"null"),
TemplateTextChildNode::Compound(_) => out.extend_from_slice(b"null"),
},
VNodeChildren::Simple(exp) => {
if exp.is_static {
out.push(b'"');
out.extend_from_slice(escape_js_string(&exp.content).as_bytes());
out.push(b'"');
} else {
out.extend_from_slice(exp.content.as_bytes());
}
}
_ => out.extend_from_slice(b"null"),
}
}