Skip to content

Commit

Permalink
docs(book): 📝 finished docs for v0.2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Sep 21, 2021
1 parent 3a9f44a commit c7d3ea2
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 6 deletions.
14 changes: 8 additions & 6 deletions docs/next/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

- [`define_app!`](./define-app.md)
- [Writing Views](./views.md)
- [Debugging](./debugging.md)
- [Templates and Routing](./templates/intro.md)
- [Modifying the `<head>`](./templates/metadata-modification.md)
- [Error Pages](./error-pages.md)
Expand All @@ -18,12 +19,13 @@
- [Using Translations](./i18n/using.md)
- [Translations Managers](./i18n/translations-managers.md)
- [Other Translation Engines](./i18n/other-engines.md)
- [Rendering Strategies]()
- [Build Paths]()
- [Build State]()
- [Request State]()
- [Revalidation]()
- [Incremental Generation]()
- [Rendering Strategies](./strategies/intro.md)
- [Build State](./strategies/build-state.md)
- [Build Paths](./strategies/build-paths.md)
- [Request State](./strategies/request-state.md)
- [Revalidation](./strategies/revalidation.md)
- [Incremental Generation](./strategies/incremental.md)
- [State Amalgamation](./strategies/amlagamation.md)
- [CLI](./cli.md)
- [Ejecting](./ejecting.md)
- [Config Managers](./config-managers.md)
Expand Down
5 changes: 5 additions & 0 deletions docs/next/src/debugging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Debugging

If you're used to Rust, you might be expecting to be able to call `println!` or `dbg!` to easily print a value to the browser console while working on an app, however this is unfortunately not yet the case (this is an issue in the lower-level libraries that Perseus depends on).

However, Perseus exports a macro called `web_log!` that can be used to print to the console. It accepts syntax identical to `format!`, `println!`, and the like and behaves in the same way, but it will print to the browser console instead of the terminal.
13 changes: 13 additions & 0 deletions docs/next/src/strategies/amlagamation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# State Amalgamation

In the introduction to this section, we mentioned that all these rendering strategies are compatible with one another, though we didn't explain how the two strategies that generate unique properties for a template can possible be compatible. That is, how can you use *build state* and *request state* in the same template? To our knowledge, Perseus is the only framework in the world (in any language) that supports using both, and it's made possible by *state amalgamation*, which lets you provide an arbitrary function that can merge conflicting states from these two strategies!

## Usage

Here's an example from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/showcase/src/templates/amalgamation.rs):

```rust,no_run,no-playground
{{#include ../../../../examples/showcase/src/templates/amalgamation.rs}}
```

This example illustrates a very simple amalgamation, taking the states of both strategies to produce a new state that combines the two. Note that this also uses `StringResultWithCause` as a return type (see the section on the [*build state*](./build-state.md) strategy for more information). It will be passed an instance of `States`, which you can learn more about in the [API docs](https://docs.rs/perseus).
17 changes: 17 additions & 0 deletions docs/next/src/strategies/build-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Build Paths

As touched on in the documentation on the *build state* strategy, Perseus can easily turn one template into many pages (e.g. one blog post template into many blog post pages) with the *build paths* strategy, which is a function that returns a `Vec<String>` of paths to build.

Note that it's often unwise to use this strategy to render all your blog posts or the like, but only render the top give most commonly accessed or the like, if any at all. This is relevant mostly when you have a large number of pages to be generated. The *incremental generation* strategy is better suited for this, and it also allows you to never need to rebuild your site for new content (as long as the server can access the new content).

Note that, like *build state*, this strategy may be invoked at build-time or while the server is running if you use the *revalidation* strategy (*incremental generation* doesn't affect *build paths* though).

## Usage

Here's the same example as given in the previous section (taken from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/showcase/src/templates/post.rs)), which uses *build paths* together with *build state* and *incremental generation*:

```rust,no_run,no_playground
{{#include ../../../../examples/showcase/src/templates/post.rs}}
```

Note the return type of the `get_build_paths` function, which returns a vector of `String`s on success or a `String` error.
31 changes: 31 additions & 0 deletions docs/next/src/strategies/build-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Build State

The most commonly-used rendering strategy for Perseus is static generation, which renders your pages to static HTML files. These can then be served by the server with almost no additional processing, which makes for an extremely fast experience!

Note that, depending on other strategies used, Perseus may call this strategy at build-time or while the server is running, so you shouldn't depend on anything only present in a build environment (particularly if you're using the *incremental generation* or *revalidation* strategies).

*Note: Perseus currently still requires a server if you want to export to purely static files, though standalone exports may be added in a future release. In the meantime, check out [Zola](https://getzola.org), which does pure static generation fantastically.*

## Usage

### Without *Build Paths* or *Incremental Generation*

On its own, this strategy will simply generate properties for your template to turn it into a page, which would be perfect for something like a list of blog posts (just fetch the list from the filesystem, a database, etc.). Here's an example from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/showcase/src/templates/index.rs) for a simple greeting:

```rust,no_run,no_playground
{{#include ../../../../examples/showcase/src/templates/index.rs}}
```

Note that Perseus passes around properties to pages as `String`s, so the function used for this strategy is expected to return a string. Note also the return type `StringResultWithCause`, which means you can specify an error as `(String, perseus::errors::ErrorCause)`, the later part of which can either be `Client(Option<u16>)` or `Server(Option<u16>)`. The `u16`s allow specifying a custom HTTP status code, otherwise the defaults are *400* and *500* respectively. This return type allows specifying who's responsible for an error. This is irrelevant if you use this strategy on its own or with *build paths*, but if you bring in *incremental generation*, this will be necessary.

### With *Build Paths* or *Incremental Generation*

You may have noticed in the above example that the build state function takes a `path` parameter. This becomes useful once you bring the *build paths* or *incremental generation* strategies into play, which allow you to render many paths for a single template. In the following example (taken from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/showcase/src/templates/post.rs)), all three strategies are used together to pre-render some blog posts at build-time, and allow the rest to be requested and rendered if they exist (here, any post will exist except one called `tests`):

```rust,no_run,no_playground
{{#include ../../../../examples/showcase/src/templates/post.rs}}
```

When either of these additional strategies are used, *build state* will be passed the path of the page to be rendered, which allows it to prepare unique properties for that page. In the above example, it just turns the URL into a title and renders that.

For further details on *build paths* and *incremental generation*, see the following sections.
19 changes: 19 additions & 0 deletions docs/next/src/strategies/incremental.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Incremental Generation

Arguable the most powerful strategy in Perseus is *incremental generation*, which is an extension of *build paths* such that any path in the template's root path domain (more info on that concept [here](../templates/intro.md)) will result in calling the *build state* strategy while the server is running.

A perfect example of this would be an retail site with thousands of products, all using the `product` template. If we built all these with *build paths*, and they all require fetching information from a database, builds could take a very long time. Instead, it's far more efficient to use *incremental generation*, which will allow any path under `/product` to call the *build state* strategy, which you can then use to render the product when it's first requested. This is on-demand building. But how is this different from the *request state* strategy? It caches the pages after they've been built the first time, meaning **you build once on-demand, and then it's static generation from there**. In other words, this strategy provides support for rendering thousands, millions, or even billions of pages from a single template while maintaining static generation times of less than a second!

Also, this strategy is fully compatible with *build paths*, meaning you could pre-render you most common pages at build-time, and have the rest built on-demand and then cached.

## Usage

This is the simplest strategy in Perseus to enable, needing only one line of code. Here's the example from earlier (which you can find [here](https://github.com/arctic-hen7/perseus/blob/main/examples/showcase/src/templates/post.rs)) that uses *incremental generation* together with *build paths* (and of course *build state*, which is mandatory for *incremental generation* to work):

```rust,no_run,no_playground
{{#include ../../../../examples/showcase/src/templates/post.rs}}
```

All we need to do is run `.incremental_generation()` on the `Template`, and it's ready.

Note that this example throws a *404 Not Found* error if we go to `/post/tests`, which is considered an illegal URL. This is a demonstration of preventing certain pages from working with this strategy, and such filtering should be done in the *build state* strategy.
3 changes: 3 additions & 0 deletions docs/next/src/strategies/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Rendering Strategies

This section describes Perseus' rendering strategies, which differentiate it from every other framework in the world right now. Note that all the strategies detailed here can be used together, and the [showcase example](https://github.com/arctic-hen7/perseus/tree/main/examples/showcase) is the best example of seeing how each one can be used.
26 changes: 26 additions & 0 deletions docs/next/src/strategies/request-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Request State

While build-time strategies fulfill many use-cases, there are also scenarios in which you may need access to information only available at request-time, like an authentication key that the client sends over HTTP as a cookie. For these cases, Perseus supports the *request state* strategy, which is akin to traditional server-side rendering, whereby you render the page when a client requests it.

If you can avoid this strategy, do, because it will bring your app's TTFB (time to first byte) down, remember that anything done in this strategy is done on the server while the client is waiting for a page.

## Usage

Here's an example taken from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/showcase/src/templates/ip.rs) of using this strategy to tell the user their own IP address (albeit not hugely reliably as this header can be trivially spoofed, but this is for demonstration purposes):

```rust,no-run,no_playground
{{#include ../../../../examples/showcase/src/templates/ip.rs}}
```

Note that, just like *build state*, this strategy generates stringified properties that will be passed to the page to render it, and it also uses `StringResultWithCause` (see the section on [build state](./build-state.md) for more information). The key difference though is that this strategy receives a second, very powerful parameter: the HTTP request that the user sent (`perseus::Request`).

<details>
<summary>How do you get the user's request information?</summary>

[Actix Web](https://actix.rs) (and any other framework worth its salt) automatically passes this information to handlers like Perseus. The slightly difficult thing is then converting this from Actix's custom format to Perseus' (which is just an alias for the [`http`](https://docs.rs/http) module's). This is done in the [`perseus-actix-web`](https://docs.rs/perseus-actix-web) crate.

</details>

That parameter is actually just an alias for [this](https://docs.rs/http/0.2/http/request/struct.Request.html), which gives you access to all manner of things in the user's HTTP request. The main one we're concerned with in this example though is `X-Forwarded-For`, which contains the user's IP address (unless it's trivially spoofed). Because we can't assume that any HTTP header exists, we fall back to a message saying the IP address is hidden if we can't access the header.

The other notable thing in the above example is the commented-out line at the beginning of `get_request_state`, which shows you how to return an error if the client didn't provide something that they should've.
39 changes: 39 additions & 0 deletions docs/next/src/strategies/revalidation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Revalidation

While the *build state* and *build paths* strategies are excellent for generating pages efficiently, they can't be updated for new content. For example, using these strategies alone, you'd need to rebuild a blog every time you added a new post, even if those posts were stored in a database. With *revalidation*, you can avoid this by instructing Perseus to rebuild a template if certain criteria are met when it's requested.

There are two types of revalidation: time-based and logic-based. The former lets you re-render a template every 24 hours or the like, while the latter allows you to re-render a template if an arbitrary function returns `true`.

## Time-Based Revalidation Usage

Here's an example of time-based revalidation from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/showcase/src/templates/time.rs) (note that this uses *incremental generation* as well):

```rust,no_run,no_playground
{{#include ../../../../examples/showcase/src/templates/time.rs}}
```

This page displays the time at which it was built (fetched with *build state*), but rebuilds every five seconds. Note that this doesn't translate to the server's actually rebuilding it every five seconds, but rather the server will rebuild it at the next request if more than five seconds have passed since it was last built (meaning templates on the same build schedule will likely go our of sync quickly).

### Time Syntax

Perseus uses a very simple syntax inspired by [this JavaScript project]() to specify time intervals in the form `xXyYzZ` (e.g. `1w`, `5s`, `1w5s`), where the lower-case letters are number and the upper-case letters are intervals, the supported of which are listed below:

- `s`: second,
- `m`: minute,
- `h`: hour,
- `d`: day,
- `w`: week,
- `M`: month (30 days used here, 12M ≠ 1y!),
- `y`: year (365 days always, leap years ignored, if you want them add them as days)

## Logic-Based Revalidation Usage

Here's an example of logic-based revalidation from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/showcase/src/templates/time_root.rs) (actually, this example uses both types of revalidation):

```rust,no_run,no_playground
{{#include ../../../../examples/showcase/src/templates/time_root.rs}}
```

If it were just `.should_revalidate_fn()` being called here, this page would always be rebuilt every time it's requested (the closure always returns `true`, note that errors would be `String`s), however, the additional usage of time-based revalidation regulates this, and the page will only be rebuilt every five seconds. In short, your arbitrary revalidation logic will only be executed at the intervals of your time-based revalidation intervals (if none are set, it will run on every request).

Note that you should avoid lengthy operations in revalidation if at all possible, as, like the *request state* strategy, this logic will be executed while the client is waiting for their page to load.

0 comments on commit c7d3ea2

Please sign in to comment.