use crate::runtime::{Helper, Runtime, Vocabulary, vocabulary_for};
use crate::write::{Emitted, LinkSink, Writer};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderProperty {
Render,
SsrRender,
}
impl RenderProperty {
const fn name(self) -> &'static str {
match self {
Self::Render => "render",
Self::SsrRender => "ssrRender",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderFunctionName {
Render,
SfcRender,
SsrRender,
}
impl RenderFunctionName {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Render => "render",
Self::SfcRender => "_sfc_render",
Self::SsrRender => "ssrRender",
}
}
#[must_use]
pub const fn component_field(self) -> &'static str {
match self {
Self::Render | Self::SfcRender => "render",
Self::SsrRender => "ssrRender",
}
}
#[must_use]
pub const fn placement(self) -> RenderPlacement<'static> {
RenderPlacement::Function {
binding: self.as_str(),
property: match self {
Self::Render | Self::SfcRender => RenderProperty::Render,
Self::SsrRender => RenderProperty::SsrRender,
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderPlacement<'a> {
None,
Function {
binding: &'a str,
property: RenderProperty,
},
Inline,
}
#[derive(Debug)]
pub enum ScriptPart<L: LinkSink> {
Body(Writer<L>),
Inline {
before_render: Writer<L>,
after_render: Writer<L>,
},
}
#[derive(Debug)]
pub struct ModuleParts<'a, L: LinkSink> {
pub vocabulary: &'a Vocabulary,
pub prelude: Option<Writer<L>>,
pub script: Option<ScriptPart<L>>,
pub render: Option<Writer<L>>,
pub placement: RenderPlacement<'a>,
pub component: &'a str,
pub scope_id: Option<ScopeId<'a>>,
}
impl<'a, L: LinkSink> ModuleParts<'a, L> {
pub fn for_runtime(
runtime: Runtime,
version: &str,
component: &'a str,
) -> Result<Self, AssemblyError> {
Ok(Self {
vocabulary: vocabulary_for(runtime, version)
.ok_or(AssemblyError::UnsupportedRuntimeVersion)?,
prelude: None,
script: None,
render: None,
placement: RenderPlacement::None,
component,
scope_id: None,
})
}
}
mod imports;
pub mod ordinary;
mod output;
pub use output::OutputModule;
mod scope;
pub use scope::ScopeId;
pub mod setup;
#[cfg(test)]
mod template_tests;
pub fn assemble_template<L: LinkSink>(
render: Writer<L>,
vocabulary: &Vocabulary,
) -> Result<Emitted<L>, AssemblyError> {
assemble_template_with_prelude(Writer::default(), render, vocabulary)
}
pub fn assemble_template_with_prelude<L: LinkSink>(
prelude: Writer<L>,
render: Writer<L>,
vocabulary: &Vocabulary,
) -> Result<Emitted<L>, AssemblyError> {
let mut body = prelude;
end_line(&mut body);
body.push("\nexport ");
body.append(render);
let preamble = imports::preamble(vocabulary, body.helpers())?;
Ok(body.finish_with_preamble(preamble))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssemblyError {
UnsupportedRuntimeVersion,
MissingRender,
UnexpectedRender,
MissingInlineScript,
UnexpectedInlineScript,
UnknownHelper(Helper),
InvalidModuleSpecifier,
InvalidScopeId,
}
pub fn assemble<L: LinkSink>(parts: ModuleParts<'_, L>) -> Result<Emitted<L>, AssemblyError> {
let ModuleParts {
vocabulary,
prelude,
script,
render,
placement,
component,
scope_id,
} = parts;
let mut body = prelude.unwrap_or_default();
separate_statement(&mut body);
match (placement, script, render) {
(RenderPlacement::None, _, Some(_)) => return Err(AssemblyError::UnexpectedRender),
(RenderPlacement::None, Some(ScriptPart::Inline { .. }), None) => {
return Err(AssemblyError::UnexpectedInlineScript);
}
(RenderPlacement::None, script, None) => append_script(&mut body, script, component),
(RenderPlacement::Function { .. } | RenderPlacement::Inline, _, None) => {
return Err(AssemblyError::MissingRender);
}
(RenderPlacement::Function { .. }, Some(ScriptPart::Inline { .. }), Some(_)) => {
return Err(AssemblyError::UnexpectedInlineScript);
}
(RenderPlacement::Function { binding, property }, script, Some(render)) => {
append_script(&mut body, script, component);
separate_statement(&mut body);
body.append(render);
end_line(&mut body);
body.push(component);
body.push(".");
body.push(property.name());
body.push(" = ");
body.push(binding);
body.push("\n");
}
(
RenderPlacement::Inline,
Some(ScriptPart::Inline {
before_render,
after_render,
}),
Some(render),
) => {
body.append(before_render);
body.append(render);
body.append(after_render);
end_line(&mut body);
}
(RenderPlacement::Inline, _, Some(_)) => return Err(AssemblyError::MissingInlineScript),
}
if let Some(scope_id) = scope_id {
body.push(component);
body.push(".__scopeId = \"");
body.push(scope_id.as_str());
body.push("\"\n");
}
body.push("export default ");
body.push(component);
body.push("\n");
let preamble = imports::preamble(vocabulary, body.helpers())?;
Ok(body.finish_with_preamble(preamble))
}
fn append_script<L: LinkSink>(
body: &mut Writer<L>,
script: Option<ScriptPart<L>>,
component: &str,
) {
if let Some(ScriptPart::Body(script)) = script {
body.append(script);
} else {
body.push("const ");
body.push(component);
body.push(" = {}\n");
}
end_line(body);
}
fn end_line<L: LinkSink>(writer: &mut Writer<L>) {
if !writer.is_empty() && !writer.as_str().ends_with('\n') {
writer.push("\n");
}
}
fn separate_statement<L: LinkSink>(writer: &mut Writer<L>) {
if !writer.is_empty() {
end_line(writer);
writer.push(";\n");
}
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod runtime_tests;