Merge branch 'master' into intigrate-collect-assets

This commit is contained in:
Evan Almloff committed 2023-09-17 19:40:14 -05:00
1 parent ffb5de91d1
commit f976e08f71
379 files changed
+1962 -13517

No files matched your search

+2 -3
View File
@@ -6,7 +6,6 @@ edition = "2018"
description = "Dioxus render-to-string"
license = "MIT OR Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com/docs/0.3/guide/en/getting_started/ssr.html"
keywords = ["dom", "ui", "gui", "react", "ssr"]
[dependencies]
@@ -15,14 +14,14 @@ askama_escape = "0.10.3"
thiserror = "1.0.23"
rustc-hash = "1.1.0"
lru = "0.10.0"
log = "0.4.13"
tracing = { workspace = true }
http = "0.2.9"
tokio = { version = "1.28", features = ["full"], optional = true }
[dev-dependencies]
dioxus = { workspace = true }
thiserror = { workspace = true }
log = { workspace = true }
tracing = { workspace = true }
fern = { version = "0.6.0", features = ["colored"] }
anyhow = "1.0"
argh = "0.1.4"
+4 -9
View File
@@ -6,10 +6,11 @@
</div>
## Resources
This crate is a part of the broader Dioxus ecosystem. For more resources about Dioxus, check out:
- [Getting Started](https://dioxuslabs.com/docs/0.3/guide/en/getting_started/index.html)
- [Book](https://dioxuslabs.com/docs/0.3/guide/en/)
- [Getting Started](https://dioxuslabs.com/learn/0.4/getting_started)
- [Book](https://dioxuslabs.com/learn/0.4/)
- [Examples](https://github.com/DioxusLabs/example-projects)
## Overview
@@ -26,7 +27,6 @@ let text = dioxus_ssr::render(&vdom);
assert_eq!(text, "<div>hello world!</div>")
```
## Basic Usage
The simplest example is to simply render some `rsx!` nodes to HTML. This can be done with the [`render_lazy`] API.
@@ -50,15 +50,11 @@ let _ = vdom.rebuild();
let content = dioxus_ssr::render(&vdom);
```
## Usage in pre-rendering
This crate is particularly useful in pre-generating pages server-side and then selectively loading Dioxus client-side to pick up the reactive elements.
This crate supports hydration out of the box. However, both the client and server must generate the *exact* same VirtualDOMs - the client picks up its VirtualDOM assuming that the pre-rendered page output is the same. To do this, you need to make sure that your VirtualDOM implementation is deterministic! This could involve either serializing our app state and sending it to the client, hydrating only parts of the page, or building tests to ensure what's rendered on the server is the same as the client.
This crate supports hydration out of the box. However, both the client and server must generate the _exact_ same VirtualDOMs - the client picks up its VirtualDOM assuming that the pre-rendered page output is the same. To do this, you need to make sure that your VirtualDOM implementation is deterministic! This could involve either serializing our app state and sending it to the client, hydrating only parts of the page, or building tests to ensure what's rendered on the server is the same as the client.
With pre-rendering enabled, this crate will generate element nodes with Element IDs pre-associated. During hydration, the Dioxus-WebSys renderer will attach the Virtual nodes to these real nodes after a page query.
@@ -107,5 +103,4 @@ let text = render_lazy!(rsx!( div { "hello world" } ));
Dioxus SSR is a powerful tool to generate static sites. Using Dioxus for static site generation _is_ a bit overkill, however. The new documentation generation library, Doxie, is essentially Dioxus SSR on steroids designed for static site generation with client-side hydration.
Again, simply render the VirtualDOM to a string using `render` or any of the other render methods.
+6
View File
@@ -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}\"")?;
}
+5 -5
View File
@@ -139,13 +139,13 @@ impl IncrementalRenderer {
let age = elapsed.as_secs();
if let Some(invalidate_after) = self.invalidate_after {
if elapsed < invalidate_after {
log::trace!("memory cache hit {:?}", route);
tracing::trace!("memory cache hit {:?}", route);
output.write_all(cache_hit).await?;
let max_age = invalidate_after.as_secs();
return Ok(Some(RenderFreshness::new(age, max_age)));
}
} else {
log::trace!("memory cache hit {:?}", route);
tracing::trace!("memory cache hit {:?}", route);
output.write_all(cache_hit).await?;
return Ok(Some(RenderFreshness::new_age(age)));
}
@@ -157,7 +157,7 @@ impl IncrementalRenderer {
if let Ok(file) = tokio::fs::File::open(file_path.full_path).await {
let mut file = BufReader::new(file);
tokio::io::copy_buf(&mut file, output).await?;
log::trace!("file cache hit {:?}", route);
tracing::trace!("file cache hit {:?}", route);
self.promote_memory_cache(&route);
return Ok(Some(freshness));
}
@@ -184,7 +184,7 @@ impl IncrementalRenderer {
let freshness = self
.render_and_cache(route, component, props, output, rebuild_with, renderer)
.await?;
log::trace!("cache miss");
tracing::trace!("cache miss");
Ok(freshness)
}
}
@@ -206,7 +206,7 @@ impl IncrementalRenderer {
}
// if the timestamp is invalid or passed, delete the file
if let Err(err) = std::fs::remove_file(entry.path()) {
log::error!("Failed to remove file: {}", err);
tracing::error!("Failed to remove file: {}", err);
}
}
}
+89 -22
View File
@@ -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,81 @@ fn to_string_works() {
assert_eq!(out, "<div class=\"asdasdasd\" class=\"asdasdasd\" id=\"id-123\">Hello world 1 --&gt;123&lt;-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>&lt;/diiiiiiiiv&gt;<div>finalize 0</div><div>finalize 1</div><div>finalize 2</div><div>finalize 3</div><div>finalize 4</div></div>");
}
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(()),
}
}
+37
View File
@@ -0,0 +1,37 @@
use dioxus::prelude::*;
#[test]
fn static_boolean_attributs() {
fn app(cx: Scope) -> Element {
render! {
div { hidden: "false" }
div { hidden: "true" }
}
}
let mut dom = VirtualDom::new(app);
_ = dom.rebuild();
assert_eq!(
dioxus_ssr::render(&dom),
r#"<div></div><div hidden="true"></div>"#
);
}
#[test]
fn dynamic_boolean_attributs() {
fn app(cx: Scope) -> Element {
render! {
div { hidden: false }
div { hidden: true }
}
}
let mut dom = VirtualDom::new(app);
_ = dom.rebuild();
assert_eq!(
dioxus_ssr::render(&dom),
r#"<div></div><div hidden=true></div>"#
);
}
+6 -5
View File
@@ -2,11 +2,12 @@ use dioxus::prelude::*;
#[test]
fn simple() {
fn app(cx: Scope) -> Element {
#[component]
fn App(cx: Scope) -> Element {
render! { div { "hello!" } }
}
let mut dom = VirtualDom::new(app);
let mut dom = VirtualDom::new(App);
_ = dom.rebuild();
assert_eq!(dioxus_ssr::render(&dom), "<div>hello!</div>");
@@ -44,8 +45,8 @@ fn dynamic() {
#[test]
fn components() {
#[inline_props]
fn my_component(cx: Scope, name: i32) -> Element {
#[component]
fn MyComponent(cx: Scope, name: i32) -> Element {
render! {
div { "component {name}" }
}
@@ -55,7 +56,7 @@ fn components() {
dioxus_ssr::render_lazy(rsx! {
div {
(0..5).map(|name| rsx! {
my_component { name: name }
MyComponent { name: name }
})
}
}),