Create a Static Site Generation platform; Deduplicate hot reloading code (#2226)

* create static site generation helpers in the router crate

* work on integrating static site generation into fullstack

* move ssg into a separate crate

* integrate ssg with the launch builder

* simplify ssg example

* fix static_routes for child routes

* move CLI hot reloading websocket code into dioxus-hot-reload

* fix some unused imports

* use the same hot reloading websocket code for fullstack

* fix fullstack hot reloading

* move cli hot reloading logic into the hot reload crate

* ssg example working with dx serve

* add more examples

* fix clippy

* fix formatting

* fix hot reload doctest imports

* fix axum imports

* don't run server doc tests

* Fix hot reload websocket doc examples
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-05-22 07:24:15 -05:00
1 parent 460b70e0f0
commit 245003a5d4
59 files changed
+1507 -791

No files matched your search

+6 -2
View File
@@ -2,7 +2,11 @@ use dioxus_lib::prelude::*;
use std::{cell::RefCell, rc::Rc, str::FromStr};
use crate::{prelude::Outlet, routable::Routable, router_cfg::RouterConfig};
use crate::{
prelude::{provide_router_context, Outlet},
routable::Routable,
router_cfg::RouterConfig,
};
/// The config for [`Router`].
#[derive(Clone)]
@@ -78,7 +82,7 @@ where
use crate::prelude::{outlet::OutletContext, RouterContext};
use_hook(|| {
provide_context(RouterContext::new(
provide_router_context(RouterContext::new(
(props
.config
.config
+36 -1
View File
@@ -9,11 +9,37 @@ use dioxus_lib::prelude::*;
use crate::{
navigation::NavigationTarget,
prelude::{AnyHistoryProvider, IntoRoutable},
prelude::{AnyHistoryProvider, IntoRoutable, SiteMapSegment},
routable::Routable,
router_cfg::RouterConfig,
};
/// This context is set in the root of the virtual dom if there is a router present.
#[derive(Clone, Copy)]
struct RootRouterContext(Signal<Option<RouterContext>>);
/// Try to get the router that was created closest to the root of the virtual dom. This may be called outside of the router.
///
/// This will return `None` if there is no router present or the router has not been created yet.
pub fn root_router() -> Option<RouterContext> {
if let Some(ctx) = ScopeId::ROOT.consume_context::<RootRouterContext>() {
ctx.0.cloned()
} else {
ScopeId::ROOT.provide_context(RootRouterContext(Signal::new_in_scope(None, ScopeId::ROOT)));
None
}
}
pub(crate) fn provide_router_context(ctx: RouterContext) {
if root_router().is_none() {
ScopeId::ROOT.provide_context(RootRouterContext(Signal::new_in_scope(
Some(ctx),
ScopeId::ROOT,
)));
}
provide_context(ctx);
}
/// An error that can occur when navigating.
#[derive(Debug, Clone)]
pub struct ExternalNavigationFailure(pub String);
@@ -39,6 +65,8 @@ struct RouterContextInner {
failure_external_navigation: fn() -> Element,
any_route_to_string: fn(&dyn Any) -> String,
site_map: &'static [SiteMapSegment],
}
impl RouterContextInner {
@@ -119,6 +147,8 @@ impl RouterContext {
})
.to_string()
},
site_map: R::SITE_MAP,
};
// set the updater
@@ -281,6 +311,11 @@ impl RouterContext {
write_inner.update_subscribers();
}
/// Get the site map of the router.
pub fn site_map(&self) -> &'static [SiteMapSegment] {
self.inner.read().site_map
}
pub(crate) fn render_error(&self) -> Element {
let inner_read = self.inner.write_unchecked();
inner_read
+15 -50
View File
@@ -3,12 +3,8 @@ use core::pin::Pin;
use std::future::Future;
use std::str::FromStr;
use dioxus_lib::prelude::*;
use dioxus_ssr::incremental::{
IncrementalRenderer, IncrementalRendererError, RenderFreshness, WrapBody,
};
use crate::prelude::*;
use dioxus_lib::prelude::*;
/// Pre-cache all static routes.
pub async fn pre_cache_static_routes<Rt, R: WrapBody + Send + Sync>(
@@ -19,51 +15,20 @@ where
Rt: Routable,
<Rt as FromStr>::Err: std::fmt::Display,
{
for route in Rt::SITE_MAP
.iter()
.flat_map(|seg| seg.flatten().into_iter())
{
// check if this is a static segment
let mut is_static = true;
let mut full_path = String::new();
for segment in &route {
match segment {
SegmentType::Child => {}
SegmentType::Static(s) => {
full_path += "/";
full_path += s;
}
_ => {
// skip routes with any dynamic segments
is_static = false;
break;
}
}
}
if is_static {
match Rt::from_str(&full_path) {
Ok(route) => {
render_route(
renderer,
route,
&mut tokio::io::sink(),
|vdom| {
Box::pin(async move {
vdom.rebuild_in_place();
vdom.wait_for_suspense().await;
})
},
wrapper,
)
.await?;
}
Err(e) => {
tracing::info!("@ route: {}", full_path);
tracing::error!("Error pre-caching static route: {}", e);
}
}
}
for route in Rt::static_routes() {
render_route(
renderer,
route,
&mut tokio::io::sink(),
|vdom| {
Box::pin(async move {
vdom.rebuild_in_place();
vdom.wait_for_suspense().await;
})
},
wrapper,
)
.await?;
}
Ok(())
+1
View File
@@ -35,6 +35,7 @@ mod contexts {
pub(crate) mod router;
pub use navigator::*;
pub(crate) use router::*;
pub use router::{root_router, RouterContext};
}
mod router_cfg;
+47 -26
View File
@@ -233,23 +233,6 @@ type SiteMapFlattened<'a> = FlatMap<
fn(&SiteMapSegment) -> Vec<Vec<SegmentType>>,
>;
fn seg_strs_to_route<T>(segs_maybe: &Option<Vec<&str>>) -> Option<T>
where
T: Routable,
{
if let Some(str) = seg_strs_to_str(segs_maybe) {
T::from_str(&str).ok()
} else {
None
}
}
fn seg_strs_to_str(segs_maybe: &Option<Vec<&str>>) -> Option<String> {
segs_maybe
.as_ref()
.map(|segs| String::from('/') + &segs.join("/"))
}
/// Something that can be:
/// 1. Converted from a route.
/// 2. Converted to a route.
@@ -354,16 +337,20 @@ pub trait Routable: FromStr + Display + Clone + 'static {
/// Example static route: `#[route("/static/route")]`
fn static_routes() -> Vec<Self> {
Self::flatten_site_map()
.filter_map(|route| {
let route_if_static = &route
.iter()
.map(|segment| match segment {
SegmentType::Static(s) => Some(*s),
_ => None,
})
.collect::<Option<Vec<_>>>();
.filter_map(|segments| {
let mut route = String::new();
for segment in segments.iter() {
match segment {
SegmentType::Static(s) => {
route.push('/');
route.push_str(s)
}
SegmentType::Child => {}
_ => return None,
}
}
seg_strs_to_route(route_if_static)
route.parse().ok()
})
.collect()
}
@@ -413,6 +400,40 @@ pub enum SegmentType {
Child,
}
impl SegmentType {
/// Try to convert this segment into a static segment.
pub fn to_static(&self) -> Option<&'static str> {
match self {
SegmentType::Static(s) => Some(*s),
_ => None,
}
}
/// Try to convert this segment into a dynamic segment.
pub fn to_dynamic(&self) -> Option<&'static str> {
match self {
SegmentType::Dynamic(s) => Some(*s),
_ => None,
}
}
/// Try to convert this segment into a catch all segment.
pub fn to_catch_all(&self) -> Option<&'static str> {
match self {
SegmentType::CatchAll(s) => Some(*s),
_ => None,
}
}
/// Try to convert this segment into a child segment.
pub fn to_child(&self) -> Option<()> {
match self {
SegmentType::Child => Some(()),
_ => None,
}
}
}
impl Display for SegmentType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {