Merge branch 'master' into many_optional_attributes
This commit is contained in:
569 files changed
+8815
-21321
No files matched your search
@@ -1,6 +1,8 @@
|
||||
use dioxus_core::prelude::*;
|
||||
use std::fmt::Write;
|
||||
|
||||
use crate::renderer::{str_truthy, BOOL_ATTRS};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StringCache {
|
||||
pub segments: Vec<Segment>,
|
||||
@@ -86,6 +88,10 @@ impl StringCache {
|
||||
inner_html = Some(value);
|
||||
} else if let Some("style") = namespace {
|
||||
styles.push((name, value));
|
||||
} else if BOOL_ATTRS.contains(name) {
|
||||
if str_truthy(value) {
|
||||
write!(chain, " {name}=\"{value}\"",)?;
|
||||
}
|
||||
} else {
|
||||
write!(chain, " {name}=\"{value}\"")?;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
use async_trait::async_trait;
|
||||
use dioxus_core::ScopeState;
|
||||
use dioxus_html::prelude::{EvalError, EvalProvider, Evaluator};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Provides the SSREvalProvider through [`cx.provide_context`].
|
||||
pub fn init_eval(cx: &ScopeState) {
|
||||
let provider: Rc<dyn EvalProvider> = Rc::new(SSREvalProvider {});
|
||||
cx.provide_context(provider);
|
||||
}
|
||||
|
||||
/// Reprents the ssr-target's provider of evaluators.
|
||||
pub struct SSREvalProvider;
|
||||
impl EvalProvider for SSREvalProvider {
|
||||
fn new_evaluator(&self, _: String) -> Result<Rc<dyn Evaluator>, EvalError> {
|
||||
Ok(Rc::new(SSREvaluator) as Rc<dyn Evaluator + 'static>)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a ssr-target's JavaScript evaluator.
|
||||
pub struct SSREvaluator;
|
||||
|
||||
// In ssr rendering we never run or resolve evals.
|
||||
#[async_trait(?Send)]
|
||||
impl Evaluator for SSREvaluator {
|
||||
/// Runs the evaluated JavaScript.
|
||||
async fn join(&self) -> Result<serde_json::Value, EvalError> {
|
||||
std::future::pending::<()>().await;
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
/// Sends a message to the evaluated JavaScript.
|
||||
fn send(&self, _el: serde_json::Value) -> Result<(), EvalError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript.
|
||||
async fn recv(&self) -> Result<serde_json::Value, EvalError> {
|
||||
std::future::pending::<()>().await;
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,7 @@ impl IncrementalRenderer {
|
||||
let mut html_buffer = WriteBuffer { buffer: Vec::new() };
|
||||
{
|
||||
let mut vdom = VirtualDom::new_with_props(comp, props);
|
||||
crate::eval::init_eval(vdom.base_scope());
|
||||
rebuild_with(&mut vdom).await;
|
||||
|
||||
renderer.render_before_body(&mut *html_buffer)?;
|
||||
|
||||
@@ -67,6 +67,7 @@ pub struct IncrementalRendererConfig {
|
||||
memory_cache_limit: usize,
|
||||
invalidate_after: Option<Duration>,
|
||||
map_path: Option<PathMapFn>,
|
||||
clear_cache: bool,
|
||||
}
|
||||
|
||||
impl Default for IncrementalRendererConfig {
|
||||
@@ -83,9 +84,16 @@ impl IncrementalRendererConfig {
|
||||
memory_cache_limit: 10000,
|
||||
invalidate_after: None,
|
||||
map_path: None,
|
||||
clear_cache: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the cache on startup (default: true)
|
||||
pub fn clear_cache(mut self, clear_cache: bool) -> Self {
|
||||
self.clear_cache = clear_cache;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a mapping from the route to the file path. This will override the default mapping configured with `static_dir`.
|
||||
/// The function should return the path to the folder to store the index.html file in.
|
||||
pub fn map_path<F: Fn(&str) -> PathBuf + Send + Sync + 'static>(mut self, map_path: F) -> Self {
|
||||
@@ -114,7 +122,7 @@ impl IncrementalRendererConfig {
|
||||
/// Build the incremental renderer.
|
||||
pub fn build(self) -> IncrementalRenderer {
|
||||
let static_dir = self.static_dir.clone();
|
||||
IncrementalRenderer {
|
||||
let mut renderer = IncrementalRenderer {
|
||||
static_dir: self.static_dir.clone(),
|
||||
memory_cache: NonZeroUsize::new(self.memory_cache_limit)
|
||||
.map(|limit| lru::LruCache::with_hasher(limit, Default::default())),
|
||||
@@ -129,6 +137,12 @@ impl IncrementalRendererConfig {
|
||||
path
|
||||
})
|
||||
}),
|
||||
};
|
||||
|
||||
if self.clear_cache {
|
||||
renderer.invalidate_all();
|
||||
}
|
||||
|
||||
renderer
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
mod cache;
|
||||
pub mod config;
|
||||
@@ -8,6 +10,7 @@ pub mod incremental;
|
||||
#[cfg(feature = "incremental")]
|
||||
mod incremental_cfg;
|
||||
|
||||
pub mod eval;
|
||||
pub mod renderer;
|
||||
pub mod template;
|
||||
|
||||
@@ -40,6 +43,7 @@ pub fn render_lazy(f: LazyNodes<'_, '_>) -> String {
|
||||
};
|
||||
|
||||
let mut dom = VirtualDom::new_with_props(lazy_app, props);
|
||||
crate::eval::init_eval(dom.base_scope());
|
||||
_ = dom.rebuild();
|
||||
|
||||
Renderer::new().render(&dom)
|
||||
|
||||
+177
-22
@@ -1,5 +1,7 @@
|
||||
use super::cache::Segment;
|
||||
use crate::cache::StringCache;
|
||||
|
||||
use dioxus_core::Attribute;
|
||||
use dioxus_core::{prelude::*, AttributeValue, DynamicNode, RenderReturn};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
@@ -83,18 +85,13 @@ impl Renderer {
|
||||
inner_html = Some(attr);
|
||||
} else if attr.namespace == Some("style") {
|
||||
accumulated_dynamic_styles.push(attr);
|
||||
} else if BOOL_ATTRS.contains(&attr.name) {
|
||||
if truthy(&attr.value) {
|
||||
write!(buf, " {}=", attr.name)?;
|
||||
write_value(buf, &attr.value)?;
|
||||
}
|
||||
} else {
|
||||
match attr.value {
|
||||
AttributeValue::Text(value) => {
|
||||
write!(buf, " {}=\"{}\"", attr.name, value)?
|
||||
}
|
||||
AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?,
|
||||
AttributeValue::Int(value) => write!(buf, " {}={}", attr.name, value)?,
|
||||
AttributeValue::Float(value) => {
|
||||
write!(buf, " {}={}", attr.name, value)?
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
write_attribute(buf, attr)?;
|
||||
}
|
||||
}
|
||||
Segment::Node(idx) => match &template.dynamic_nodes[*idx] {
|
||||
@@ -153,17 +150,9 @@ impl Renderer {
|
||||
write!(buf, " style=\"")?;
|
||||
}
|
||||
for attr in &accumulated_dynamic_styles {
|
||||
match attr.value {
|
||||
AttributeValue::Text(value) => {
|
||||
write!(buf, "{}:{};", attr.name, value)?
|
||||
}
|
||||
AttributeValue::Bool(value) => {
|
||||
write!(buf, "{}:{};", attr.name, value)?
|
||||
}
|
||||
AttributeValue::Float(f) => write!(buf, "{}:{};", attr.name, f)?,
|
||||
AttributeValue::Int(i) => write!(buf, "{}:{};", attr.name, i)?,
|
||||
_ => {}
|
||||
};
|
||||
write!(buf, "{}:", attr.name)?;
|
||||
write_value_unquoted(buf, &attr.value)?;
|
||||
write!(buf, ";")?;
|
||||
}
|
||||
if !*inside_style_tag {
|
||||
write!(buf, "\"")?;
|
||||
@@ -248,3 +237,169 @@ fn to_string_works() {
|
||||
|
||||
assert_eq!(out, "<div class=\"asdasdasd asdasdasd\" id=\"id-123\">Hello world 1 -->123<-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div></diiiiiiiiv><div>finalize 0</div><div>finalize 1</div><div>finalize 2</div><div>finalize 3</div><div>finalize 4</div></div>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_for_loop_works() {
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
render! {
|
||||
div { class: "asdasdasd",
|
||||
for _ in (0..5) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
_ = dom.rebuild();
|
||||
|
||||
let mut renderer = Renderer::new();
|
||||
let out = renderer.render(&dom);
|
||||
|
||||
for item in renderer.template_cache.iter() {
|
||||
if item.1.segments.len() > 5 {
|
||||
assert_eq!(
|
||||
item.1.segments,
|
||||
vec![
|
||||
PreRendered("<div class=\"asdasdasd\"".into(),),
|
||||
Attr(0,),
|
||||
StyleMarker {
|
||||
inside_style_tag: false,
|
||||
},
|
||||
PreRendered(">".into()),
|
||||
InnerHtmlMarker,
|
||||
PreRendered("</div>".into(),),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
use Segment::*;
|
||||
|
||||
assert_eq!(out, "<div class=\"asdasdasd\"></div>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_render_works() {
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
render! {}
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
_ = dom.rebuild();
|
||||
|
||||
let mut renderer = Renderer::new();
|
||||
let out = renderer.render(&dom);
|
||||
|
||||
for item in renderer.template_cache.iter() {
|
||||
if item.1.segments.len() > 5 {
|
||||
assert_eq!(item.1.segments, vec![]);
|
||||
}
|
||||
}
|
||||
assert_eq!(out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_rsx_works() {
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn app(_: Scope) -> Element {
|
||||
rsx! {};
|
||||
None
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
_ = dom.rebuild();
|
||||
|
||||
let mut renderer = Renderer::new();
|
||||
let out = renderer.render(&dom);
|
||||
|
||||
for item in renderer.template_cache.iter() {
|
||||
if item.1.segments.len() > 5 {
|
||||
assert_eq!(item.1.segments, vec![]);
|
||||
}
|
||||
}
|
||||
assert_eq!(out, "");
|
||||
}
|
||||
|
||||
pub(crate) const BOOL_ATTRS: &[&str] = &[
|
||||
"allowfullscreen",
|
||||
"allowpaymentrequest",
|
||||
"async",
|
||||
"autofocus",
|
||||
"autoplay",
|
||||
"checked",
|
||||
"controls",
|
||||
"default",
|
||||
"defer",
|
||||
"disabled",
|
||||
"formnovalidate",
|
||||
"hidden",
|
||||
"ismap",
|
||||
"itemscope",
|
||||
"loop",
|
||||
"multiple",
|
||||
"muted",
|
||||
"nomodule",
|
||||
"novalidate",
|
||||
"open",
|
||||
"playsinline",
|
||||
"readonly",
|
||||
"required",
|
||||
"reversed",
|
||||
"selected",
|
||||
"truespeed",
|
||||
"webkitdirectory",
|
||||
];
|
||||
|
||||
pub(crate) fn str_truthy(value: &str) -> bool {
|
||||
!value.is_empty() && value != "0" && value.to_lowercase() != "false"
|
||||
}
|
||||
|
||||
pub(crate) fn truthy(value: &AttributeValue) -> bool {
|
||||
match value {
|
||||
AttributeValue::Text(value) => str_truthy(value),
|
||||
AttributeValue::Bool(value) => *value,
|
||||
AttributeValue::Int(value) => *value != 0,
|
||||
AttributeValue::Float(value) => *value != 0.0,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write_attribute(buf: &mut impl Write, attr: &Attribute) -> std::fmt::Result {
|
||||
let name = &attr.name;
|
||||
match attr.value {
|
||||
AttributeValue::Text(value) => write!(buf, " {name}=\"{value}\""),
|
||||
AttributeValue::Bool(value) => write!(buf, " {name}={value}"),
|
||||
AttributeValue::Int(value) => write!(buf, " {name}={value}"),
|
||||
AttributeValue::Float(value) => write!(buf, " {name}={value}"),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write_value(buf: &mut impl Write, value: &AttributeValue) -> std::fmt::Result {
|
||||
match value {
|
||||
AttributeValue::Text(value) => write!(buf, "\"{}\"", value),
|
||||
AttributeValue::Bool(value) => write!(buf, "{}", value),
|
||||
AttributeValue::Int(value) => write!(buf, "{}", value),
|
||||
AttributeValue::Float(value) => write!(buf, "{}", value),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write_value_unquoted(
|
||||
buf: &mut impl Write,
|
||||
value: &AttributeValue,
|
||||
) -> std::fmt::Result {
|
||||
match value {
|
||||
AttributeValue::Text(value) => write!(buf, "{}", value),
|
||||
AttributeValue::Bool(value) => write!(buf, "{}", value),
|
||||
AttributeValue::Int(value) => write!(buf, "{}", value),
|
||||
AttributeValue::Float(value) => write!(buf, "{}", value),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user