Skip to content

Commit

Permalink
revert(templates): ⏪ reverted problematic is_client checks
Browse files Browse the repository at this point in the history
This feature is tracked by #26, but will not be released in v0.2.3.
  • Loading branch information
arctic-hen7 committed Sep 26, 2021
1 parent 06c7c88 commit 0fc173e
Show file tree
Hide file tree
Showing 9 changed files with 40 additions and 66 deletions.
22 changes: 8 additions & 14 deletions docs/0.2.x/src/templates/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ At the core of Perseus is its template system, which is how you'll define every

## Templates vs Pages

In Perseus, the idea of a *template* is very different to the idea of a *page*.
In Perseus, the idea of a _template_ is very different to the idea of a _page_.

A *page* corresponds to a URL in your app, like the about page, the landing page, or an individual blog post.
A _page_ corresponds to a URL in your app, like the about page, the landing page, or an individual blog post.

A *template* can generate *many* pages or only one by using *rendering strategies*.
A _template_ can generate _many_ pages or only one by using _rendering strategies_.

The best way to illustrate this is with the example of a simple blog, with each page stored in something like a CMS (content management system). This app would only need two templates (in addition to a landing page, an about page, etc.): `blog` and `post`. For simplicity, we'll put the list of all blog posts in `blog`, and then each post will have its own URL under `post`.

The `blog` template will be rendered to `/blog`, and will only use the *build state* strategy, fetching a list of all our posts from the CMS every time the blog is rebuilt (or you could use revalidation and incremental generation to mean you never have to rebuild at all, but that's beyond the scope of this section). This template only generates one page, providing it the properties of the list of blog posts. So, in this case, the `blog` template has generated the `/blog` page.
The `blog` template will be rendered to `/blog`, and will only use the _build state_ strategy, fetching a list of all our posts from the CMS every time the blog is rebuilt (or you could use revalidation and incremental generation to mean you never have to rebuild at all, but that's beyond the scope of this section). This template only generates one page, providing it the properties of the list of blog posts. So, in this case, the `blog` template has generated the `/blog` page.

The `post` template is more complex, and it will generate *many* pages, one for each blog post. This would probably use the *build paths* strategy, which lets you fetch a list of blog posts from the CMS at build-time and invoke *build state* for each of them, which would then get their content, metadata, etc. Thus, the `post` template generates many pages.
The `post` template is more complex, and it will generate _many_ pages, one for each blog post. This would probably use the _build paths_ strategy, which lets you fetch a list of blog posts from the CMS at build-time and invoke _build state_ for each of them, which would then get their content, metadata, etc. Thus, the `post` template generates many pages.

Hopefully that explains the difference between a template and a post. This is a somewhat unintuitive part of Perseus, but it should be clear in the documentation what the difference is. Note however that old versions of the examples in the repository used these terms interchangeably, when they used to be the same. If you see any remaining ambiguity in the docs, please [open an issue](https://github.com/arctic-hen7/perseus/issues/new/choose)!

Expand All @@ -34,9 +34,9 @@ There's a list of all the methods available on a template [here](https://docs.rs

Perseus' routing system is basically invisible, there's no router that you need to work with, nor any place for you to define explicit routes. Instead, Perseus automatically infers the routes for all your templates and the pages they generate from their names!

The general rule is this: a template called `X` will be rendered at `/X`. Simple. What's more difficult to understand is what we call *template path domains*, which is the system that makes route inference possible. **A template can only ever generate pages within the scope of its root path.** Its root path is its name. In the example of a template called `X`, it can render `/X/Y`, `/X/Y/Z`, etc., but it can *never* render `/A`.
The general rule is this: a template called `X` will be rendered at `/X`. Simple. What's more difficult to understand is what we call _template path domains_, which is the system that makes route inference possible. **A template can only ever generate pages within the scope of its root path.** Its root path is its name. In the example of a template called `X`, it can render `/X/Y`, `/X/Y/Z`, etc., but it can _never_ render `/A`.

To generate paths within a template's domain, you can use the *build paths* and *incremental generation* strategies (more on those later). Both of these support dynamic parameters (which might be denoted in other languages as `/post/<title>/info` or the like).
To generate paths within a template's domain, you can use the _build paths_ and _incremental generation_ strategies (more on those later). Both of these support dynamic parameters (which might be denoted in other languages as `/post/<title>/info` or the like).

### Dynamic Parameters Above the Domain

Expand All @@ -50,10 +50,4 @@ There is one use-case though that requires a bit more fiddling: having a differe

## Checking Render Context

It's often necessary to make sure you're only running some logic on the client-side, particularly anything to do with `web_sys`, which will `panic!` if used on the server. Because Perseus renders your templates in both environments, you'll need to explicitly check if you want to do something only on the client (like get an authentication token from a cookie). This can be done trivially with the `is_client!` macro, which does exactly what it says on the tin. Here's an example from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/basic/src/templates/about.rs):

```rust,no_run,no_playground
{{#include ../../../../examples/basic/src/templates/about.rs}}
```

This is a very contrived example, but what you should note if you try this is the flash from `server` to `client`, because the page is pre-rendered on the server and then hydrated on the client. This is an important principle of Perseus, and you should be aware of this potential flashing (easily solved by a less contrived example).
> This feature is currently in development, the tracking issue is available [here](https://github.com/arctic-hen7/perseus/issues/26).
22 changes: 8 additions & 14 deletions docs/next/src/templates/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ At the core of Perseus is its template system, which is how you'll define every

## Templates vs Pages

In Perseus, the idea of a *template* is very different to the idea of a *page*.
In Perseus, the idea of a _template_ is very different to the idea of a _page_.

A *page* corresponds to a URL in your app, like the about page, the landing page, or an individual blog post.
A _page_ corresponds to a URL in your app, like the about page, the landing page, or an individual blog post.

A *template* can generate *many* pages or only one by using *rendering strategies*.
A _template_ can generate _many_ pages or only one by using _rendering strategies_.

The best way to illustrate this is with the example of a simple blog, with each page stored in something like a CMS (content management system). This app would only need two templates (in addition to a landing page, an about page, etc.): `blog` and `post`. For simplicity, we'll put the list of all blog posts in `blog`, and then each post will have its own URL under `post`.

The `blog` template will be rendered to `/blog`, and will only use the *build state* strategy, fetching a list of all our posts from the CMS every time the blog is rebuilt (or you could use revalidation and incremental generation to mean you never have to rebuild at all, but that's beyond the scope of this section). This template only generates one page, providing it the properties of the list of blog posts. So, in this case, the `blog` template has generated the `/blog` page.
The `blog` template will be rendered to `/blog`, and will only use the _build state_ strategy, fetching a list of all our posts from the CMS every time the blog is rebuilt (or you could use revalidation and incremental generation to mean you never have to rebuild at all, but that's beyond the scope of this section). This template only generates one page, providing it the properties of the list of blog posts. So, in this case, the `blog` template has generated the `/blog` page.

The `post` template is more complex, and it will generate *many* pages, one for each blog post. This would probably use the *build paths* strategy, which lets you fetch a list of blog posts from the CMS at build-time and invoke *build state* for each of them, which would then get their content, metadata, etc. Thus, the `post` template generates many pages.
The `post` template is more complex, and it will generate _many_ pages, one for each blog post. This would probably use the _build paths_ strategy, which lets you fetch a list of blog posts from the CMS at build-time and invoke _build state_ for each of them, which would then get their content, metadata, etc. Thus, the `post` template generates many pages.

Hopefully that explains the difference between a template and a post. This is a somewhat unintuitive part of Perseus, but it should be clear in the documentation what the difference is. Note however that old versions of the examples in the repository used these terms interchangeably, when they used to be the same. If you see any remaining ambiguity in the docs, please [open an issue](https://github.com/arctic-hen7/perseus/issues/new/choose)!

Expand All @@ -34,9 +34,9 @@ There's a list of all the methods available on a template [here](https://docs.rs

Perseus' routing system is basically invisible, there's no router that you need to work with, nor any place for you to define explicit routes. Instead, Perseus automatically infers the routes for all your templates and the pages they generate from their names!

The general rule is this: a template called `X` will be rendered at `/X`. Simple. What's more difficult to understand is what we call *template path domains*, which is the system that makes route inference possible. **A template can only ever generate pages within the scope of its root path.** Its root path is its name. In the example of a template called `X`, it can render `/X/Y`, `/X/Y/Z`, etc., but it can *never* render `/A`.
The general rule is this: a template called `X` will be rendered at `/X`. Simple. What's more difficult to understand is what we call _template path domains_, which is the system that makes route inference possible. **A template can only ever generate pages within the scope of its root path.** Its root path is its name. In the example of a template called `X`, it can render `/X/Y`, `/X/Y/Z`, etc., but it can _never_ render `/A`.

To generate paths within a template's domain, you can use the *build paths* and *incremental generation* strategies (more on those later). Both of these support dynamic parameters (which might be denoted in other languages as `/post/<title>/info` or the like).
To generate paths within a template's domain, you can use the _build paths_ and _incremental generation_ strategies (more on those later). Both of these support dynamic parameters (which might be denoted in other languages as `/post/<title>/info` or the like).

### Dynamic Parameters Above the Domain

Expand All @@ -50,10 +50,4 @@ There is one use-case though that requires a bit more fiddling: having a differe

## Checking Render Context

It's often necessary to make sure you're only running some logic on the client-side, particularly anything to do with `web_sys`, which will `panic!` if used on the server. Because Perseus renders your templates in both environments, you'll need to explicitly check if you want to do something only on the client (like get an authentication token from a cookie). This can be done trivially with the `is_client!` macro, which does exactly what it says on the tin. Here's an example from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/basic/src/templates/about.rs):

```rust,no_run,no_playground
{{#include ../../../../examples/basic/src/templates/about.rs}}
```

This is a very contrived example, but what you should note if you try this is the flash from `server` to `client`, because the page is pre-rendered on the server and then hydrated on the client. This is an important principle of Perseus, and you should be aware of this potential flashing (easily solved by a less contrived example).
> This feature is currently in development, the tracking issue is available [here](https://github.com/arctic-hen7/perseus/issues/26).
14 changes: 1 addition & 13 deletions examples/basic/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
use perseus::{is_client, Template};
use perseus::Template;
use std::rc::Rc;
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};

#[component(AboutPage<G>)]
pub fn about_page() -> SycamoreTemplate<G> {
template! {
p { "About." }
p {
(
format!(
"This is currently being run on the {}.",
if is_client!() {
"client"
} else {
"server"
}
)
)
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/i18n/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use perseus::{t, Template, Translator};
use perseus::{t, Template};
use std::rc::Rc;
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};

Expand Down
2 changes: 1 addition & 1 deletion examples/i18n/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use perseus::{link, t, Template, Translator};
use perseus::{link, t, Template};
use std::rc::Rc;
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};

Expand Down
8 changes: 6 additions & 2 deletions packages/perseus/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ pub async fn build_template(
.await?;
// Prerender the template using that state
let prerendered = sycamore::render_to_string(|| {
template.render_for_template(Some(initial_state.clone()), Rc::clone(&translator))
template.render_for_template(
Some(initial_state.clone()),
Rc::clone(&translator),
true,
)
});
// Write that prerendered HTML to a static file
config_manager
Expand Down Expand Up @@ -104,7 +108,7 @@ pub async fn build_template(
// It's safe to add a property to the render options here because `.is_basic()` will only return true if path generation is not being used (or anything else)
if template.is_basic() {
let prerendered = sycamore::render_to_string(|| {
template.render_for_template(None, Rc::clone(&translator))
template.render_for_template(None, Rc::clone(&translator), true)
});
let head_str = template.render_head_str(None, Rc::clone(&translator));
// Write that prerendered HTML to a static file
Expand Down
6 changes: 3 additions & 3 deletions packages/perseus/src/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ async fn render_request_state(
let state = Some(template.get_request_state(path.to_string(), req).await?);
// Use that to render the static HTML
let html = sycamore::render_to_string(|| {
template.render_for_template(state.clone(), Rc::clone(&translator))
template.render_for_template(state.clone(), Rc::clone(&translator), true)
});
let head = template.render_head_str(state.clone(), Rc::clone(&translator));

Expand Down Expand Up @@ -142,7 +142,7 @@ async fn revalidate(
.await?,
);
let html = sycamore::render_to_string(|| {
template.render_for_template(state.clone(), Rc::clone(&translator))
template.render_for_template(state.clone(), Rc::clone(&translator), true)
});
let head = template.render_head_str(state.clone(), Rc::clone(&translator));
// Handle revalidation, we need to parse any given time strings into datetimes
Expand Down Expand Up @@ -254,7 +254,7 @@ pub async fn get_page_for_template(
// We need to generate and cache this page for future usage
let state = Some(template.get_build_state(path.to_string()).await?);
let html_val = sycamore::render_to_string(|| {
template.render_for_template(state.clone(), Rc::clone(&translator))
template.render_for_template(state.clone(), Rc::clone(&translator), true)
});
let head_val = template.render_head_str(state.clone(), Rc::clone(&translator));
// Handle revalidation, we need to parse any given time strings into datetimes
Expand Down
3 changes: 2 additions & 1 deletion packages/perseus/src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ pub async fn app_shell(
// BUG (Sycamore): this will double-render if the component is just text (no nodes)
sycamore::hydrate_to(
// This function provides translator context as needed
|| template.render_for_template(state, Rc::clone(&translator)),
|| template.render_for_template(state, Rc::clone(&translator), false),
&container_rx_elem,
);
checkpoint("page_interactive");
Expand Down Expand Up @@ -341,6 +341,7 @@ pub async fn app_shell(
template.render_for_template(
page_data.state,
Rc::clone(&translator),
false,
)
},
&container_rx_elem,
Expand Down
27 changes: 10 additions & 17 deletions packages/perseus/src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ use std::pin::Pin;
use std::rc::Rc;
use sycamore::context::{ContextProvider, ContextProviderProps};
use sycamore::prelude::{template, GenericNode, Template as SycamoreTemplate};
use wasm_bindgen::prelude::*;
use wasm_bindgen::{JsCast, JsValue};

/// Used to encapsulate whether or not a template is running on the client or server. We use a `struct` so as not to interfere with
/// any user-set context.
#[derive(Clone, Debug)]
pub struct RenderCtx {
/// Whether or not we're being executed on the server-side.
pub is_server: bool,
}

/// Represents all the different states that can be generated for a single template, allowing amalgamation logic to be run with the knowledge
/// of what did what (rather than blindly working on a vector).
Expand Down Expand Up @@ -211,8 +217,10 @@ impl<G: GenericNode> Template<G> {
&self,
props: Option<String>,
translator: Rc<Translator>,
_is_server: bool,
) -> SycamoreTemplate<G> {
template! {
// TODO tell templates where they're being rendered
// We provide the translator through context, which avoids having to define a separate variable for every translation due to Sycamore's `template!` macro taking ownership with `move` closures
ContextProvider(ContextProviderProps {
value: Rc::clone(&translator),
Expand Down Expand Up @@ -475,20 +483,5 @@ macro_rules! get_templates_map {
};
}

/// Checks in a template if the code is being run on client-side or the server-side. This will work anywhere in your code, and will
/// return `true` for any browser environment (including web workers).
pub fn is_client() -> bool {
#[wasm_bindgen]
extern "C" {
type Global;

#[wasm_bindgen(method, getter, js_name = Window)]
fn window(this: &Global) -> JsValue;
}

let global: Global = js_sys::global().unchecked_into();
!global.window().is_undefined()
}

/// A type alias for a `HashMap` of `Template`s.
pub type TemplateMap<G> = HashMap<String, Template<G>>;

0 comments on commit 0fc173e

Please sign in to comment.