Skip to content

Commit

Permalink
docs: removed old docs and refactored substantially
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Jan 14, 2023
1 parent a4c1dfb commit 0c8a9be
Show file tree
Hide file tree
Showing 17 changed files with 73 additions and 356 deletions.
3 changes: 1 addition & 2 deletions docs/next/en-US/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@
# Capsules

- [Introduction](/docs/capsules/intro)
- [Capsules vs. templates](/docs/capsules/capsules-vs-templates)
- [Delayed widgets](/docs/capsules/delayed)
- [Using capsules](/docs/capsules/using)

# Miscellaneous

Expand Down
61 changes: 61 additions & 0 deletions docs/next/en-US/capsules/using.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Using capsules

Using capsules in your own code involves a few steps. First, you'll want to create a `capsules/` directory in the root of your project (this is just convention), which is just like `templates/`, but (surprise surprise) for capsules. You'll also probably want to bring [`lazy_static`](https://docs.rs/lazy_static/latest/lazy_static) into your project as a dependency so you can use the *referential defin pattern* of capsule definition. This means, rather than having something like a `get_capsule()` function that you use to get your capsule, you create a static reference to it that you can use from anywhere in your program. This is because, unlike templates, capsules get used in more than one place than just `PerseusApp`: you'll also need them where you want to interpolate them into templates.

## `Capsule` vs. `Template`

When you're creating a capsule, there's a new type to get familiar with, called [`Capsule`](=prelude/struct.Capsule@perseus), which works similarly to the templates you're used to, but it's also slightly different. Basically, you want to start with a `Template`, define all your state generation stuff on there (but not your views), then pass that to `Capsule::build()` *without* alling `.build()` on it, and then specify your capsule-specific properties on `Capsule`. It's best to give an example:

```
{{#include ../../../examples/core/capsules/src/capsules/greeting.rs}}
```

This is a very simple capsule analogous to the *Hello world!* template we built in the first tutorial, but it shows how capsules generally work. You have the definition of the capsule as a static reference up the top (here using a `get_capsule()` function, but you can do whatever you like here). Notice the use of [`PerseusNodeType`](=prelude/type.PerseusNodeType@perseus), which denotes the correct rendering backend based on system circumstances (namely the presence of the `hydrate` feature and the `engine`/`client` flags), since generics aren't allowed in lazy statics. This will be reconciled using internal transmutes with whatever `G` you use in your templates (this is unsafe code internally, but your app will panic if it thinks it will undergo any undefined behavior, and this should really never happen unless you're doing some extremely wacky things).

Notice that `greeting_capsule`, our view function, is almost identical to that for a template, except it takes a third argument `props` for the properties, which just need to be `Clone`, and that also makes the second generic on `Capsule` itself.

In the `get_capsule` function, you can see we're creating a `Template` with the name of the capsule (which will be gated behind a special internal prefix, so it won't conflict with any of your templates), and then we declare state generation functions and the like on there (like [build-time state](:state/build)). This is *identical* to a full template.

Then, we set the *fallback function*, which is a view that will be used while widgets that this capsule produces are still being fetched: this is usually a loader or something similar, but here we're just using `.empty_fallback()` to use an empty view. Any capsules without fallback functions will fail the Perseus build process.

We then use `.view_with_state()`, which might look exactly the same as the template one, but remember that capsule functions take an extra argument! They also have completely different internal logic compared to templates, so make sure you're defining your views on the `Capsule` rather than the `Template`! (If you make a mistake here, your capsules will simply be blank, rather than causing untold internal errors.)

Finally, we call `.build()` to turn that all into a proper `Capsule`.

## Using widgets

Here's an example of a page that uses some widgets:

```
{{#include ../../../examples/core/capsules/src/templates/index.rs}}
```

This template uses two widgets: one called `LINKS`, and another called `WRAPPER` (which is a wrapper over the `GREETING` capsule we defined in the previous example). To use these, we interpolate them like variables into a Sycamore `view!` using the `.widget()` function, which takes three arguments: the Sycamore scope, *the path to the widget*, and the properties. For capsules that have no properties, we use the unit type `()`.

Note that there is no place where we have to declare all the widgets a page uses, and they can even be state dependent (e.g. only if the state property `foo` is set to `5` do we render a widget from the `BAR` capsule). Perseus will figure out which widgets a page uses by actually rendering it. This also means that you can nest widgets (as in the `WRAPPER` capsule in the above example), but don't do too much nesting, since the best Perseus can do is build each layer one at a time, meaning, if you have five layers of nesting, it will take five sequential re-renders to render your whole page on the engine-side (a similar thing goes for fetching on the client-side).

### Widget paths

That second argument to the capsule's `.widget()` function is by far the most important, and this is why we've emphasized that idea of **template + page = state**. Based on that, and Perseus' routing systems, any template `foo` will render pages under `/foo/` on your website. So this argument is what goes under `/foo/`. Let's say we have a capsule `ALPHABET` that renders one widget for every letter of the Latin alphabet: we might put it `/a` as our path if we wanted to render the `__capsule/alphabet/a` widget, or `/x` if we wanted to render `__capsule/alphabet/x`. (That `__capsule` prefix is applied internally, and you'll only need it if you're manually querying Perseus' API.)

In the above example, we used the empty string to denote the index page, because the capsules we're using only render one widget each.

if you want to see a more advanced example of a capsule that uses incremental generation to build widgets, check out [this code].

*Note: while it might seem extremely weird, there is nothing to stop you from reactively changing the widgets you render on the client-side, as in [this example].*

## Delayed widgets

As explained earlier, Perseus automatically collates all widgets into one HTMl page before serving an *initial load*, for speed, but sometimes this is undesirable. Sometimes, no matter what, you want one section of your page to be loaded after the rest of the page is ready, usually to improve page load times by delaying the load of a heavy section. This can be done trivially by replacing `.widget()` with `.delayed_widget()`. Yep, that's all.

*Note: currently, delayed widgets don't work with hydration, which a bug we're working on. For now, you'll need to disable the `hydrate` feature flag if you want to use them.*

## Rescheduling

One of the trickiest parts of the capsule system to build internally centered around this problem: what should Perseus do when you create a page that uses build state, and make that use a widget that uses request state? The page would normally be rendered at build-time, but it can't be, because it's waiting on a widget. In these cases, Perseus needs to *reschedule* the build of such pages to request-time, which it needs your permission to do (since this will reduce performance). When you're building your app, you should keep this in the back of your mind, and be prepared for rescheduling errors that might arise in the build process: these can always be solved by adding `.allow_rescheduling()` to the definition of a template that fits these properties. Do *not* pre-emptively add `.allow_rescheduling()`, wait for the actual error to make sure you need it (there are some cases where Perseus can optimize things).

One notable instance where this isn't necessary is in incremental generation. For example, let's say you have a capsule `number` that has a build paths listing of `1`, `2`, and `3`, but it can incrementally render any number. Then let's say you have a page called `four` that uses `number/4` --- typically, Perseus would wait until somebody requested the `foo/4` widget to render it, but here it's being very clearly used at build-time, so Perseus will figure out what you mean and just render it at build-time anyway. This means you don't have to laboriously keep all your build paths in sync, which can lead to faster development and fewer maintainence headaches.

## Support

The Perseus capsules system is not only very new, it is a completely novel architecture, so there are bound to be bugs and idiosyncracies that can be improved. If you're having problems, even if you don't think they're a bug, please let us know through [a GitHub discussion], or [on Discord] --- every little bit of feedback helps us improve Perseus and make it easier for you to develop lightning-fast apps! If you do reckon you've found a bug, or if you'd like to request a new feature, please open an issue [on GitHub] and let us know!
File renamed without changes.
10 changes: 10 additions & 0 deletions docs/next/en-US/first-app/deploying.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ When it's done, this command wil produce a `pkg/` folder in the root of your pro

Obviously, you probably want to host your app in production on a different address, like `0.0.0.0` (network-speak for "host this everywhere so everyone who comes to my server can find it"), and perhaps on port `80`. Note that Perseus doesn't handle HTTPS at all, and you'll need to do this with a reverse proxy or the like (which comes built-in to most servers these days). You can set the host and port with the `PERSEUS_HOST` and `PERSEUS_PORT` environment variables.

### Optimizations

When you deploy your Perseus app, there are two separate main binaries that are produced: the Wasm bundle, and the engine binary (the latter won't exist if you use export deployment though). What you want to do is optimize the engine binary for speed, since it's running your server, and the Wasm bundle for *size*: the reason is because Wasm is already extremely fast, and the main impediment to speed in the browser is how long it takes to load the Wasm bundle from the server. *Smaller bundle = faster load.* (But remember that this is only for making your pages interactive, the user will see content straight away!)

Most of these optimizations are all applied automatically in `perseus deploy`, but they can be tweaked if you like by setting some of the flags on the CLI (which you can see with `perseus deploy --help`). These will allow you to apply different optimization settings to suit your needs.

One thing you may want to do is replace Rust's default allocator (thing in charge of your app's memory) with something slower but smaller. There are two options here: [`wee_alloc`] (which has memory leaks, and is now unmaintained), and the newer (but largely untested) [`lol_alloc`]. Whatever you do, make sure you only use these with `#[cfg(client)]` to make sure they don't get used for your server as well! (Since that would *massively* slow down your site.)

For more information on optimizing Wasm bundle sizes, see [here](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size).

## Export deployment

However, there's actually a simpler way of deploying this app in particular. Because we aren't using any features that need a server (e.g. we're generating state at build-time, not request-time, so all the server is doing is just passing over files that it generated when we built the app), we can *export* our app. You can try this for development with `perseus export -s` (the `-s` tells Perseus to spin up a file server automatically to serve your app for you). In production, use `perseus deploy -e` to make `pkg/` contain a series of static files. If you have `python` installed on your computer, you can serve this with `python -m http.server -d pkg/`. The nice thing about exported apps is that they can be sent to places like [GitHub Pages], which will host your app for free. In fact, this whole website is exported (because it's all static documentation), and hosted on exactly that service!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ The easiest way to get Perseus to use this, instead of the usual `cargo`, is to

**Remember:** do NOT use `cargo-clif` in production!

After applying all the optimizations herein, a testing benchmark of measuring the time taken to run `perseus build` with the bleeding-edge version of the CLI in development mode on the `basic` example, changing a hardcoded state property, went from taking 28 seconds with the stable compiler and no target directory separation to just 7 seconds, when Cranelift and nightly were used along with the target directory separation now inbuilt into Perseus. In other words, you can cut Perseus' compile times by 75%! (And this was deliberately on a fairly old laptop with other programs running in the background to mimic a realistic setup.)
After applying all the optimizations herein, a testing benchmark of measuring the time taken to run `perseus build` with the bleeding-edge version of the CLI in development mode on the `basic` example, changing a hardcoded state property, went from taking 28 seconds with the stable compiler and no target directory separation to just 7 seconds, when Cranelift and nightly were used along with the target directory separation now inbuilt into Perseus. In other words, you can **cut Perseus' compile times by 75%**! (And this was deliberately on a fairly old laptop with other programs running in the background to mimic a realistic setup.)
File renamed without changes.
1 change: 0 additions & 1 deletion docs/next/en-US/reference/architecture.md

This file was deleted.

44 changes: 0 additions & 44 deletions docs/next/en-US/reference/deploying.md

This file was deleted.

Loading

0 comments on commit 0c8a9be

Please sign in to comment.