use std::{fmt, str::FromStr};
use rustc_hash::FxHashMap;
use thiserror::Error;
pub mod angular;
mod parser;
pub mod react;
mod render;
mod shared;
pub mod solid;
pub mod svelte;
pub mod vue;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_parser;
#[derive(Clone, Debug, PartialEq)]
pub struct FrameworkComponentIsland {
pub name: String,
pub props: FxHashMap<String, serde_json::Value>,
pub id: String,
pub content: Option<String>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FrameworkCodegenTarget {
React,
Svelte,
Vue,
}
impl FrameworkCodegenTarget {
pub fn as_str(self) -> &'static str {
match self {
Self::React => "react",
Self::Svelte => "svelte",
Self::Vue => "vue",
}
}
}
impl fmt::Display for FrameworkCodegenTarget {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for FrameworkCodegenTarget {
type Err = FrameworkCodegenError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"react" => Ok(Self::React),
"svelte" => Ok(Self::Svelte),
"vue" => Ok(Self::Vue),
_ => Err(FrameworkCodegenError::UnsupportedTarget { target: String::from(value) }),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FrameworkCodegenMode {
InnerHtml,
Expression,
RenderFunction,
Component,
}
impl FrameworkCodegenMode {
pub fn as_str(self) -> &'static str {
match self {
Self::InnerHtml => "innerHtml",
Self::Expression => "expression",
Self::RenderFunction => "renderFunction",
Self::Component => "component",
}
}
}
impl fmt::Display for FrameworkCodegenMode {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for FrameworkCodegenMode {
type Err = FrameworkCodegenError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"html" | "innerHtml" | "inner-html" => Ok(Self::InnerHtml),
"expression" | "native" => Ok(Self::Expression),
"renderFunction" | "render-function" => Ok(Self::RenderFunction),
"component" => Ok(Self::Component),
_ => Err(FrameworkCodegenError::UnsupportedMode { mode: String::from(value) }),
}
}
}
#[derive(Debug, Error, Eq, PartialEq)]
pub enum FrameworkCodegenError {
#[error("Unsupported framework component render target: {target}")]
UnsupportedTarget { target: String },
#[error("Unsupported framework component render mode: {mode}")]
UnsupportedMode { mode: String },
#[error("Unsupported framework component render mode {mode} for target {target}")]
UnsupportedModeForTarget { mode: FrameworkCodegenMode, target: FrameworkCodegenTarget },
}
impl FrameworkCodegenError {
pub fn message(&self) -> String {
let mut output = String::with_capacity(96);
match self {
Self::UnsupportedTarget { target } => {
output.push_str("Unsupported framework component render target: ");
output.push_str(target);
}
Self::UnsupportedMode { mode } => {
output.push_str("Unsupported framework component render mode: ");
output.push_str(mode);
}
Self::UnsupportedModeForTarget { mode, target } => {
output.push_str("Unsupported framework component render mode ");
output.push_str(mode.as_str());
output.push_str(" for target ");
output.push_str(target.as_str());
}
}
output
}
}
pub fn render_framework_component_code(
html: &str,
target: FrameworkCodegenTarget,
islands: &[FrameworkComponentIsland],
) -> String {
let nodes = parser::HtmlFragmentParser::new(html).parse();
render::FrameworkCodegen { target, islands }.render_root(&nodes)
}
pub fn render_framework_code(
html: &str,
target: FrameworkCodegenTarget,
mode: FrameworkCodegenMode,
islands: &[FrameworkComponentIsland],
) -> Result<String, FrameworkCodegenError> {
match mode {
FrameworkCodegenMode::InnerHtml => Ok(render_inner_html_component(html, target)),
FrameworkCodegenMode::Expression => render_framework_expression(html, target, islands),
FrameworkCodegenMode::RenderFunction => {
if target == FrameworkCodegenTarget::Svelte {
return Err(FrameworkCodegenError::UnsupportedModeForTarget { mode, target });
}
let expression = render_framework_expression(html, target, islands)?;
render_framework_render_function(&expression, target)
}
FrameworkCodegenMode::Component => render_framework_component(html, target, islands),
}
}
pub fn escape_svelte_markup(html: &str) -> String {
svelte::escape_markup(html)
}
fn render_framework_expression(
html: &str,
target: FrameworkCodegenTarget,
islands: &[FrameworkComponentIsland],
) -> Result<String, FrameworkCodegenError> {
match target {
FrameworkCodegenTarget::React | FrameworkCodegenTarget::Vue => {
Ok(render_framework_component_code(html, target, islands))
}
FrameworkCodegenTarget::Svelte => Err(FrameworkCodegenError::UnsupportedModeForTarget {
mode: FrameworkCodegenMode::Expression,
target,
}),
}
}
fn render_framework_render_function(
expression: &str,
target: FrameworkCodegenTarget,
) -> Result<String, FrameworkCodegenError> {
match target {
FrameworkCodegenTarget::React => Ok(react::render_function_module(expression)),
FrameworkCodegenTarget::Vue => Ok(vue::render_function_module(expression)),
FrameworkCodegenTarget::Svelte => Err(FrameworkCodegenError::UnsupportedModeForTarget {
mode: FrameworkCodegenMode::RenderFunction,
target,
}),
}
}
fn render_framework_component(
html: &str,
target: FrameworkCodegenTarget,
islands: &[FrameworkComponentIsland],
) -> Result<String, FrameworkCodegenError> {
match target {
FrameworkCodegenTarget::React => {
let expression = render_framework_component_code(html, target, islands);
Ok(react::component_module(&expression))
}
FrameworkCodegenTarget::Vue => {
let expression = render_framework_component_code(html, target, islands);
Ok(vue::component_module(&expression))
}
FrameworkCodegenTarget::Svelte => Ok(svelte::component_module(html)),
}
}
fn render_inner_html_component(html: &str, target: FrameworkCodegenTarget) -> String {
match target {
FrameworkCodegenTarget::React => react::inner_html_component_module(html),
FrameworkCodegenTarget::Svelte => svelte::inner_html_component_module(html),
FrameworkCodegenTarget::Vue => vue::inner_html_component_module(html),
}
}