Skip to content

Commit

Permalink
feat(routing): ✨ added perseus routing systems and simplified app def…
Browse files Browse the repository at this point in the history
…inition

Routing can now be derived from templates almost entirely.

BREAKING CHANGE: `define_app!` redesigned, special meaning for `index` template name, app shell takes full templates, `Locales` has new property
  • Loading branch information
arctic-hen7 committed Sep 11, 2021
1 parent c2f1091 commit 49aa2b9
Show file tree
Hide file tree
Showing 16 changed files with 304 additions and 197 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"conventionalCommits.scopes": ["i18n"]
"conventionalCommits.scopes": ["i18n", "routing"]
}
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ members = [
"packages/perseus",
"packages/perseus-actix-web",
"packages/perseus-cli",
# TODO remake showcase app for CLI
"examples/showcase",
# "examples/showcase/app",
# "examples/showcase/server-actix-web",
"examples/cli",
# We have the CLI subcrates as workspace members so we can actively develop on them
# They also can't be a workspace until nested workspaces are supported
Expand All @@ -15,3 +12,8 @@ members = [
"examples/basic",
"examples/i18n"
]

# Needed until Sycamore #230 is released
[patch.crates-io]
sycamore = { git = "https://github.com/arctic-hen7/sycamore", branch = "router-clone-stable" }
sycamore-router = { git = "https://github.com/arctic-hen7/sycamore", branch = "router-clone-stable" }
5 changes: 5 additions & 0 deletions examples/cli/.perseus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ crate-type = ["cdylib", "rlib"]
[[bin]]
name = "perseus-internal"
path = "src/bin/build.rs"

# Needed until Sycamore #230 is released (becomes relevant when excluded from user's workspaces)
[patch.crates-io]
sycamore = { git = "https://github.com/arctic-hen7/sycamore", branch = "router-clone-stable" }
sycamore-router = { git = "https://github.com/arctic-hen7/sycamore", branch = "router-clone-stable" }
5 changes: 5 additions & 0 deletions examples/cli/.perseus/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ app = { package = "perseus-example-cli", path = "../../" }
perseus-actix-web = { path = "../../../../packages/perseus-actix-web" }
actix-web = "3.3"
futures = "0.3"

# Needed until Sycamore #230 is released (becomes relevant when excluded from user's workspaces)
[patch.crates-io]
sycamore = { git = "https://github.com/arctic-hen7/sycamore", branch = "router-clone-stable" }
sycamore-router = { git = "https://github.com/arctic-hen7/sycamore", branch = "router-clone-stable" }
61 changes: 37 additions & 24 deletions examples/cli/.perseus/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
use app::{get_error_pages, get_locales, match_route, AppRoute, APP_ROUTE};
use perseus::{app_shell, ClientTranslationsManager};
use app::{get_error_pages, get_locales, get_routes, APP_ROUTE};
use perseus::router::{RouteInfo, RouteVerdict};
use perseus::{app_shell, ClientTranslationsManager, DomNode};
use std::cell::RefCell;
use std::rc::Rc;
use sycamore::prelude::template;
use sycamore::rx::{ContextProvider, ContextProviderProps};
use sycamore_router::BrowserRouter;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};

/// The entrypoint into the app itself. This will be compiled to WASM and actually executed, rendering the rest of the app.
#[wasm_bindgen]
pub fn run() -> Result<(), JsValue> {
// If we're in development, panics should go to the console
if cfg!(debug_assertions) {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
}
// Panics should always go to the console
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
// Get the root (for the router) we'll be injecting page content into
let root = web_sys::window()
.unwrap()
Expand All @@ -27,28 +27,41 @@ pub fn run() -> Result<(), JsValue> {
Rc::new(RefCell::new(ClientTranslationsManager::new(&get_locales())));
// Get the error pages in an `Rc` so we aren't creating hundreds of them
let error_pages = Rc::new(get_error_pages());
// Get the routes in an `Rc` as well
let routes = Rc::new(get_routes::<DomNode>());

sycamore::render_to(
|| {
template! {
BrowserRouter(move |route: AppRoute| {
match route {
// We handle the 404 for the user for convenience
// To get a translator here, we'd have to go async and dangerously check the URL
AppRoute::NotFound => get_error_pages().get_template_for_page("", &404, "not found", None),
// All other routes are based on the user's given statements
_ => {
let (name, render_fn, locale) = match_route(route);

app_shell(
name,
render_fn,
locale,
// We give the app shell a translations manager and let it get the `Rc<Translator>` (because it can do async safely)
Rc::clone(&translations_manager),
Rc::clone(&error_pages)
)
}
// We provide the routes in context (can't provide them directly because of Sycamore trait constraints)
// BUG: context doesn't exist when link clicked first time, works second time...
ContextProvider(ContextProviderProps {
value: Rc::clone(&routes),
children: || template! {
BrowserRouter(move |route: RouteVerdict<DomNode>| {
match route {
// Perseus' custom routing system is tightly coupled to the template system, and returns exactly what we need for the app shell!
RouteVerdict::Found(RouteInfo {
path,
template_fn,
locale
}) => app_shell(
path,
template_fn,
locale,
// We give the app shell a translations manager and let it get the `Rc<Translator>` (because it can do async safely)
Rc::clone(&translations_manager),
Rc::clone(&error_pages)
),
// If the user is using i18n, then they'll want to detect the locale on any paths missing a locale
// Those all go to the same system that redirects to the appropriate locale
// TODO locale detection
RouteVerdict::LocaleDetection(_) => get_error_pages().get_template_for_page("", &400, "locale detection not yet supported", None),
// We handle the 404 for the user for convenience
// To get a translator here, we'd have to go async and dangerously check the URL
RouteVerdict::NotFound => get_error_pages().get_template_for_page("", &404, "not found", None),
}
})
}
})
}
Expand Down
26 changes: 2 additions & 24 deletions examples/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,12 @@ mod pages;

use perseus::define_app;

#[derive(perseus::Route)]
pub enum Route {
#[to("/")]
Index,
#[to("/about")]
About,
#[not_found]
NotFound,
}
define_app! {
root: "#root",
route: Route,
router: {
Route::Index => [
"index".to_string(),
pages::index::template_fn(),
"en-US".to_string()
],
Route::About => [
"about".to_string(),
pages::about::template_fn(),
"en-US".to_string()
]
},
error_pages: crate::error_pages::get_error_pages(),
templates: [
crate::pages::index::get_page::<G>(),
crate::pages::about::get_page::<G>()
"/" => crate::pages::index::get_page::<G>(),
"/about" => crate::pages::about::get_page::<G>()
],
locales: {
default: "en-US",
Expand Down
28 changes: 3 additions & 25 deletions examples/i18n/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,13 @@ mod templates;

use perseus::define_app;

#[derive(perseus::Route)]
pub enum Route {
#[to("/<locale>")]
Index { locale: String },
#[to("/<locale>/about")]
About { locale: String },
#[not_found]
NotFound,
}

define_app! {
root: "#root",
route: Route,
router: {
Route::Index { locale } => [
"index".to_string(),
templates::index::template_fn(),
locale
],
Route::About { locale } => [
"about".to_string(),
templates::about::template_fn(),
locale
]
},
error_pages: crate::error_pages::get_error_pages(),
templates: [
crate::templates::index::get_template::<G>(),
crate::templates::about::get_template::<G>()
"/about" => crate::templates::about::get_template::<G>(),
// Note that the index page comes last, otherwise locale detection for `/about` matches `about` as a locale
"/" => crate::templates::index::get_template::<G>()
],
locales: {
default: "en-US",
Expand Down
83 changes: 9 additions & 74 deletions examples/showcase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,84 +3,19 @@ mod templates;

use perseus::define_app;

#[derive(perseus::Route)]
pub enum Route {
#[to("/<locale>")]
Index { locale: String },
#[to("/<locale>/about")]
About { locale: String },
#[to("/<locale>/post/new")]
NewPost { locale: String },
// BUG: Sycamore doesn't support dynamic paths before dynamic segments (https://github.com/sycamore-rs/sycamore/issues/228)
#[to("/post/<slug..>")]
Post { slug: Vec<String> },
#[to("/<locale>/ip")]
Ip { locale: String },
#[to("/<locale>/time")]
TimeRoot { locale: String },
#[to("/<locale>/timeisr/<slug>")]
Time { locale: String, slug: String },
#[to("/<locale>/amalgamation")]
Amalgamation { locale: String },
#[not_found]
NotFound,
}

define_app! {
root: "#root",
route: Route,
router: {
Route::Index { locale } => [
"index".to_string(),
templates::index::template_fn(),
locale
],
Route::About { locale } => [
"about".to_string(),
templates::about::template_fn(),
locale
],
Route::Post { slug } => [
format!("post/{}", slug.join("/")),
templates::post::template_fn(),
"en-US".to_string() // BUG: see above
],
Route::NewPost { locale } => [
"post/new".to_string(),
templates::new_post::template_fn(),
locale
],
Route::Ip { locale } => [
"ip".to_string(),
templates::ip::template_fn(),
locale
],
Route::Time { slug, locale } => [
format!("timeisr/{}", slug),
templates::time::template_fn(),
locale
],
Route::TimeRoot { locale } => [
"time".to_string(),
templates::time_root::template_fn(),
locale
],
Route::Amalgamation { locale } => [
"amalgamation".to_string(),
templates::amalgamation::template_fn(),
locale
]
},
error_pages: crate::error_pages::get_error_pages(),
templates: [
crate::templates::index::get_template::<G>(),
crate::templates::about::get_template::<G>(),
crate::templates::post::get_template::<G>(),
crate::templates::new_post::get_template::<G>(),
crate::templates::ip::get_template::<G>(),
crate::templates::time::get_template::<G>(),
crate::templates::time_root::get_template::<G>(),
crate::templates::amalgamation::get_template::<G>()
"/" => crate::templates::index::get_template::<G>(),
"/about" => crate::templates::about::get_template::<G>(),
"/post/new" => crate::templates::new_post::get_template::<G>(),
// BUG: Sycamore doesn't support dynamic paths before dynamic segments (https://github.com/sycamore-rs/sycamore/issues/228)
"/post/<slug..>" => crate::templates::post::get_template::<G>(),
"/ip" => crate::templates::ip::get_template::<G>(),
"/time" => crate::templates::time_root::get_template::<G>(),
"/timeisr/<slug>" => crate::templates::time::get_template::<G>(),
"/amalgamation" => crate::templates::amalgamation::get_template::<G>()
],
locales: {
default: "en-US",
Expand Down
3 changes: 3 additions & 0 deletions packages/perseus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ pub mod config_manager;
mod decode_time_str;
pub mod errors;
mod locales;
mod log;
mod macros;
/// Utilities regarding routing.
pub mod router;
/// Utilities for serving your app. These are platform-agnostic, and you probably want an integration like [perseus-actix-web](https://crates.io/crates/perseus-actix-web).
pub mod serve;
/// Utilities to do with the app shell. You probably don't want to delve into here.
Expand Down
2 changes: 2 additions & 0 deletions packages/perseus/src/locales.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pub struct Locales {
pub default: String,
/// All other supported locales, which will all be built at build time.
pub other: Vec<String>,
/// Whether or not the user is actually using i18n. This is set here because most things that need locale data also need it.
pub using_i18n: bool,
}
impl Locales {
/// Gets all the supported locales by combining the default, and other.
Expand Down
11 changes: 11 additions & 0 deletions packages/perseus/src/log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// Logs the given `format!`-style data to the browser's console.
#[macro_export]
macro_rules! web_log {
($format_str:literal $(, $data:expr)*) => {
web_sys::console::log_1(
&wasm_bindgen::JsValue::from(
format!($format_str $(, $data)*)
)
);
};
}
Loading

0 comments on commit 49aa2b9

Please sign in to comment.