Switch over to WebAssembly, Rust and Yew #35

Merged
BlakeRain merged 87 commits from yew-static into main 2023-08-30 18:01:40 +00:00
4 changed files with 82 additions and 217 deletions
Showing only changes of commit 72b3abead9 - Show all commits

View File

@ -120,26 +120,6 @@ async fn copy_resources(
Ok(()) Ok(())
} }
async fn write_data(out_dir: impl AsRef<Path>) -> std::io::Result<()> {
log::info!("Writing tags.json ...");
let tags = site::model::source::get_tags();
tokio::fs::write(
out_dir.as_ref().join("tags.json"),
serde_json::to_string(&tags)?,
)
.await?;
log::info!("Writing posts.json ...");
let posts = site::model::source::get_posts();
tokio::fs::write(
out_dir.as_ref().join("posts.json"),
serde_json::to_string(&posts)?,
)
.await?;
Ok(())
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init(); env_logger::init();
@ -176,8 +156,5 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
tokio::fs::write(path, html).await?; tokio::fs::write(path, html).await?;
} }
log::info!("Writing data");
write_data(&out_dir).await?;
Ok(()) Ok(())
} }

View File

@ -1,18 +1,87 @@
#[cfg(feature = "hydration")] use include_dir::{include_dir, Dir, File};
mod hydrating;
#[cfg(feature = "hydration")]
pub use hydrating::*;
#[cfg(not(feature = "hydration"))]
mod filesystem;
use std::collections::HashMap; use std::collections::HashMap;
use yew::{function_component, html, Children, ContextProvider, Html, Properties};
#[cfg(not(feature = "hydration"))] use super::{frontmatter::parse_front_matter, PostInfo, Tag};
pub use filesystem::*;
use super::{PostInfo, Tag};
pub type TagsContext = HashMap<String, Tag>; pub type TagsContext = HashMap<String, Tag>;
pub type PostsContext = Vec<PostInfo>; pub type PostsContext = Vec<PostInfo>;
#[derive(Properties, PartialEq)]
pub struct ProvideTagsProps {
#[prop_or_default]
pub children: Children,
}
#[function_component(ProvideTags)]
pub fn provide_tags(props: &ProvideTagsProps) -> Html {
let tags = get_tags();
html! {
<ContextProvider<TagsContext> context={tags}>
{props.children.clone()}
</ContextProvider<TagsContext>>
}
}
pub fn get_tags() -> TagsContext {
let file = CONTENT_DIR.get_file("tags.yaml").expect("tags.yaml");
match serde_yaml::from_slice::<Vec<Tag>>(file.contents()) {
Ok(tags) => tags
.into_iter()
.map(|tag| (tag.slug.clone(), tag))
.collect::<TagsContext>(),
Err(err) => {
panic!("Failed to parse tags.yaml: {err}");
}
}
}
#[derive(Properties, PartialEq)]
pub struct ProvidePostsProps {
#[prop_or_default]
pub children: Children,
}
#[function_component(ProvidePosts)]
pub fn provide_posts(props: &ProvidePostsProps) -> Html {
let posts = get_posts();
html! {
<ContextProvider<PostsContext> context={posts}>
{props.children.clone()}
</ContextProvider<PostsContext>>
}
}
pub fn get_posts() -> PostsContext {
let files = CONTENT_DIR.get_dir("posts").expect("posts dir").files();
let mut posts = files.filter_map(load_post_info).collect::<Vec<_>>();
posts.sort_by(|a, b| b.doc_info.published.cmp(&a.doc_info.published));
posts
}
fn load_post_info(file: &File) -> Option<PostInfo> {
let (Some(front_matter), matter) = parse_front_matter(file.contents()) else {
return None;
};
let slug = file
.path()
.file_stem()
.expect("filename")
.to_str()
.expect("valid file name")
.to_string();
let reading_time = words_count::count(&matter.content).words / 200;
Some(PostInfo::from_front_matter(
slug,
Some(reading_time),
matter.excerpt,
front_matter,
))
}
static CONTENT_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/content");

View File

@ -1,87 +0,0 @@
use include_dir::{include_dir, Dir, File};
use yew::{function_component, html, Children, ContextProvider, Html, Properties};
use crate::model::{
frontmatter::parse_front_matter,
source::{PostsContext, TagsContext},
PostInfo, Tag,
};
#[derive(Properties, PartialEq)]
pub struct ProvideTagsProps {
#[prop_or_default]
pub children: Children,
}
#[function_component(ProvideTags)]
pub fn provide_tags(props: &ProvideTagsProps) -> Html {
let tags = get_tags();
html! {
<ContextProvider<TagsContext> context={tags}>
{props.children.clone()}
</ContextProvider<TagsContext>>
}
}
pub fn get_tags() -> TagsContext {
let file = CONTENT_DIR.get_file("tags.yaml").expect("tags.yaml");
match serde_yaml::from_slice::<Vec<Tag>>(file.contents()) {
Ok(tags) => tags
.into_iter()
.map(|tag| (tag.slug.clone(), tag))
.collect::<TagsContext>(),
Err(err) => {
panic!("Failed to parse tags.yaml: {err}");
}
}
}
#[derive(Properties, PartialEq)]
pub struct ProvidePostsProps {
#[prop_or_default]
pub children: Children,
}
#[function_component(ProvidePosts)]
pub fn provide_posts(props: &ProvidePostsProps) -> Html {
let posts = get_posts();
html! {
<ContextProvider<PostsContext> context={posts}>
{props.children.clone()}
</ContextProvider<PostsContext>>
}
}
pub fn get_posts() -> PostsContext {
let files = CONTENT_DIR.get_dir("posts").expect("posts dir").files();
let mut posts = files.filter_map(load_post_info).collect::<Vec<_>>();
posts.sort_by(|a, b| b.doc_info.published.cmp(&a.doc_info.published));
posts
}
fn load_post_info(file: &File) -> Option<PostInfo> {
let (Some(front_matter), matter) = parse_front_matter(file.contents()) else {
return None;
};
let slug = file
.path()
.file_stem()
.expect("filename")
.to_str()
.expect("valid file name")
.to_string();
let reading_time = words_count::count(&matter.content).words / 200;
Some(PostInfo::from_front_matter(
slug,
Some(reading_time),
matter.excerpt,
front_matter,
))
}
static CONTENT_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/content");

View File

@ -1,94 +0,0 @@
use std::collections::HashMap;
use thiserror::Error;
use yew::{function_component, html, use_state, Children, ContextProvider, Html, Properties};
use yew_hooks::{use_async_with_options, UseAsyncOptions};
use crate::model::source::{PostsContext, TagsContext};
#[derive(Debug, Error, Clone, PartialEq)]
pub enum ApiError {
#[error("Unable to complete request")]
RequestError,
#[error("Unable to deserialize response")]
DeserializeError,
}
#[derive(Properties, PartialEq)]
pub struct ProvideTagsProps {
#[prop_or_default]
pub children: Children,
}
#[function_component(ProvideTags)]
pub fn provide_tags(props: &ProvideTagsProps) -> Html {
let tags = use_state(HashMap::new);
{
let tags = tags.clone();
use_async_with_options::<_, (), ApiError>(
async move {
let res = reqwest::get("/tags.json")
.await
.map_err(|err| {
log::error!("Failed to get '/tags.json': {err:?}");
ApiError::RequestError
})?
.json()
.await
.map_err(|err| {
log::error!("Failed to deserialize '/tags.json': {err:?}");
ApiError::DeserializeError
})?;
tags.set(res);
Ok(())
},
UseAsyncOptions::enable_auto(),
);
}
html! {
<ContextProvider<TagsContext> context={(*tags).clone()}>
{props.children.clone()}
</ContextProvider<TagsContext>>
}
}
#[derive(Properties, PartialEq)]
pub struct ProvidePostsProps {
#[prop_or_default]
pub children: Children,
}
#[function_component(ProvidePosts)]
pub fn provide_posts(props: &ProvidePostsProps) -> Html {
let posts = use_state(Vec::new);
{
let posts = posts.clone();
use_async_with_options::<_, (), ApiError>(
async move {
let res = reqwest::get("/posts.json")
.await
.map_err(|err| {
log::error!("Failed to get '/posts.json': {err:?}");
ApiError::RequestError
})?
.json()
.await
.map_err(|err| {
log::error!("Failed to deserialize '/posts.json': {err:?}");
ApiError::DeserializeError
})?;
posts.set(res);
Ok(())
},
UseAsyncOptions::enable_auto(),
);
}
html! {
<ContextProvider<PostsContext> context={(*posts).clone()}>
{props.children.clone()}
</ContextProvider<PostsContext>>
}
}