use vize_l0::{Span, id::NodeId};
use vize_l3::decision::ssr::{
NativeSelectedSetupSsrAnalysis, NativeSsrFileAnalysis, NativeTemplateScopedSsrAnalysis,
NativeTemplateSsrAnalysis, SsrFacts, SsrPart, SsrUnsupported,
};
use vize_l3::decision::{NativeAnalysis, policy::TargetPolicy};
use crate::module::ScopeId;
use crate::runtime::{Runtime, vocabulary};
use crate::write::{LinkSink, Writer};
mod setup;
use super::static_class;
mod write;
pub use setup::emit_selected_setup_template;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SsrError {
pub node: Option<NodeId>,
pub span: Span,
pub kind: SsrErrorKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SsrErrorKind {
WrongPolicy,
MissingAnalysis,
MissingRuntimeHelper,
Unsupported(SsrUnsupported),
OutputTooLarge,
Expression(crate::expr::EmitError),
}
pub fn emit<L: LinkSink>(analysis: &NativeAnalysis<'_, '_>) -> Result<Writer<L>, SsrError> {
if analysis.policy() != TargetPolicy::Ssr {
return Err(SsrError {
node: None,
span: Span::new(0, 0),
kind: SsrErrorKind::WrongPolicy,
});
}
encode(analysis.artifact().source(), analysis.ssr(), None, None)
}
pub fn emit_file<L: LinkSink>(
analysis: &NativeSsrFileAnalysis<'_, '_>,
) -> Result<Writer<L>, SsrError> {
encode(analysis.artifact().source(), analysis.ssr(), None, None)
}
pub fn emit_template<L: LinkSink>(
receipt: &NativeTemplateSsrAnalysis<'_, '_>,
) -> Result<Writer<L>, SsrError> {
encode(receipt.artifact().source(), receipt.ssr(), None, None)
}
pub fn emit_scoped_template<L: LinkSink>(
receipt: &NativeTemplateScopedSsrAnalysis<'_, '_>,
scope: ScopeId<'_>,
) -> Result<Writer<L>, SsrError> {
encode(
receipt.artifact().source(),
receipt.ssr(),
Some(scope),
None,
)
}
fn encode<L: LinkSink>(
source: &str,
facts: Option<&SsrFacts<'_, '_>>,
scope: Option<ScopeId<'_>>,
setup: Option<&NativeSelectedSetupSsrAnalysis<'_, '_, '_>>,
) -> Result<Writer<L>, SsrError> {
let error = |kind| SsrError {
node: None,
span: Span::new(0, source.len() as u32),
kind,
};
let facts = facts.ok_or_else(|| error(SsrErrorKind::MissingAnalysis))?;
if let Some(rejected) = facts.unsupported().first() {
return Err(SsrError {
node: Some(rejected.node),
span: rejected.span,
kind: SsrErrorKind::Unsupported(rejected.reason),
});
}
let runtime = vocabulary(Runtime::VueServerRenderer);
let attrs = runtime
.helper("ssrRenderAttrs")
.ok_or_else(|| error(SsrErrorKind::MissingRuntimeHelper))?;
let merge = runtime
.helper("mergeProps")
.ok_or_else(|| error(SsrErrorKind::MissingRuntimeHelper))?;
let mut writer = Writer::<L>::with_capacity(source.len());
writer.push(if setup.is_some() {
"function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {"
} else {
"function ssrRender(_ctx, _push, _parent, _attrs) {"
});
writer.indent();
writer.newline();
if !facts.parts().is_empty() {
writer.push("_push(`");
if facts.fragment() {
writer.push("<!--[-->");
}
for part in facts.parts() {
match *part {
SsrPart::Open { node, element, .. } => {
writer.push("<");
write::template(&mut writer, element.tag, element.span, false);
if facts.inherit_attrs() == Some(node) {
writer.use_helper(attrs);
writer.push("${_ssrRenderAttrs(");
if element.attributes.is_empty() {
writer.push("_attrs");
} else {
writer.use_helper(merge);
let multiline = element.attributes.len() > 1;
writer.push("_mergeProps({");
if multiline {
writer.indent();
writer.newline();
} else {
writer.push(" ");
}
for (index, attribute) in element.attributes.iter().enumerate() {
if index > 0 {
writer.push(",");
if multiline {
writer.newline();
} else {
writer.push(" ");
}
}
write::property(&mut writer, attribute.name, attribute.span);
writer.push(": ");
let value = static_class::value(attribute);
write::quoted(&mut writer, value.as_str(), attribute.span);
}
if multiline {
writer.deindent();
writer.newline();
} else {
writer.push(" ");
}
writer.push("}, _attrs)");
}
if element.tag.contains('-') {
writer.push(", ");
write::quoted(&mut writer, element.tag, element.span);
}
writer.push(")}");
} else {
for attribute in &element.attributes {
writer.push(" ");
write::template(&mut writer, attribute.name, attribute.span, false);
if let Some(value) = attribute.value {
let value = static_class::normalize(attribute.name, value);
writer.push("=\"");
write::template(&mut writer, value.as_str(), attribute.span, true);
writer.push("\"");
}
}
}
if let Some(scope) = scope {
writer.push(" ");
writer.push(scope.as_str());
}
writer.push(">");
}
SsrPart::Close { element, .. } => {
writer.push("</");
write::template(&mut writer, element.tag, element.span, false);
writer.push(">");
}
SsrPart::Text { text, .. } => {
write::template(&mut writer, text.content, text.span, true)
}
SsrPart::Comment { comment, .. } => {
writer.push("<!--");
write::template(&mut writer, comment.content, comment.span, false);
writer.push("-->");
}
SsrPart::Interpolation {
node,
interpolation,
} => {
let setup = setup.ok_or_else(|| error(SsrErrorKind::MissingAnalysis))?;
setup::write(&mut writer, setup, node, interpolation)?;
}
}
}
if facts.fragment() {
writer.push("<!--]-->");
}
writer.push("`)");
}
writer.deindent();
writer.newline();
writer.push("}");
if u32::try_from(writer.len()).is_err() {
return Err(error(SsrErrorKind::OutputTooLarge));
}
Ok(writer)
}