-
Notifications
You must be signed in to change notification settings - Fork 84
/
lib.rs
107 lines (101 loc) · 5.8 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use app::{get_error_pages, get_locales, get_templates_map, APP_ROOT};
use perseus::error_pages::ErrorPageData;
use perseus::router::{RouteInfo, RouteVerdict};
use perseus::shell::{get_initial_state, get_render_cfg, InitialState};
use perseus::{app_shell, create_app_route, detect_locale, ClientTranslationsManager, DomNode};
use std::cell::RefCell;
use std::rc::Rc;
use sycamore::prelude::{cloned, template, NodeRef, StateHandle};
use sycamore_router::{HistoryIntegration, Router, RouterProps};
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> {
// Panics should always go to the console
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
// Get the root we'll be injecting the router into
let root = web_sys::window()
.unwrap()
.document()
.unwrap()
.query_selector(&format!("#{}", APP_ROOT))
.unwrap()
.unwrap();
// Get the root that the server will have injected initial load content into
// This will be moved into a reactive `<div>` by the app shell
// This is an `Option<Element>` until we know we aren't doing loclae detection (in which case it wouldn't exist)
let initial_container = web_sys::window()
.unwrap()
.document()
.unwrap()
.query_selector("#__perseus_content")
.unwrap();
// And create a node reference that we can use as a handle to the reactive verison
let container_rx = NodeRef::new();
// Create a mutable translations manager to control caching
let translations_manager =
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());
// Create the router we'll use for this app, based on the user's app definition
create_app_route! {
name => AppRoute,
// The render configuration is injected verbatim into the HTML shell, so it certainly should be present
render_cfg => &get_render_cfg().expect("render configuration invalid or not injected"),
templates => &get_templates_map(),
locales => &get_locales()
}
sycamore::render_to(
|| {
template! {
Router(RouterProps::new(HistoryIntegration::new(), move |route: StateHandle<AppRoute<DomNode>>| {
wasm_bindgen_futures::spawn_local(cloned!((container_rx) => async move {
let container_rx_elem = container_rx.get::<DomNode>().unchecked_into::<web_sys::Element>();
match &route.get().as_ref().0 {
// Perseus' custom routing system is tightly coupled to the template system, and returns exactly what we need for the app shell!
// If a non-404 error occurred, it will be handled in the app shell
RouteVerdict::Found(RouteInfo {
path,
template,
locale
}) => app_shell(
path.clone(),
template.clone(),
locale.clone(),
// We give the app shell a translations manager and let it get the `Rc<Translator>` itself (because it can do async safely)
Rc::clone(&translations_manager),
Rc::clone(&error_pages),
initial_container.unwrap().clone(),
container_rx_elem.clone()
).await,
// 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
// Note that `container` doesn't exist for this scenario
RouteVerdict::LocaleDetection(path) => detect_locale(path.clone(), get_locales()),
// 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
// If this is an initial load, there'll already be an error message, so we should only proceed if the declaration is not `error`
RouteVerdict::NotFound => {
if let InitialState::Error(ErrorPageData { url, status, err }) = get_initial_state() {
// Hydrate the error pages
// Right now, we don't provide translators to any error pages that have come from the server
error_pages.hydrate_page(&url, &status, &err, None, &container_rx_elem);
} else {
get_error_pages::<DomNode>().get_template_for_page("", &404, "not found", None);
}
},
};
}));
// This template is reactive, and will be updated as necessary
// However, the server has already rendered initial load content elsewhere, so we move that into here as well in the app shell
// The main reason for this is that the router only intercepts click events from its children
template! {
div(class="__perseus_content_rx", ref=container_rx) {}
}
}))
}
},
&root,
);
Ok(())
}