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:
1 parent
460b70e0f0
commit
245003a5d4
59 files changed
+1507
-791
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user