Skip to content

Commit 0c8a9be

Browse files
committed
docs: removed old docs and refactored substantially
1 parent a4c1dfb commit 0c8a9be

17 files changed

+73
-356
lines changed

docs/next/en-US/SUMMARY.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@
5151
# Capsules
5252

5353
- [Introduction](/docs/capsules/intro)
54-
- [Capsules vs. templates](/docs/capsules/capsules-vs-templates)
55-
- [Delayed widgets](/docs/capsules/delayed)
54+
- [Using capsules](/docs/capsules/using)
5655

5756
# Miscellaneous
5857

docs/next/en-US/capsules/using.md

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Using capsules
2+
3+
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.
4+
5+
## `Capsule` vs. `Template`
6+
7+
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:
8+
9+
```
10+
{{#include ../../../examples/core/capsules/src/capsules/greeting.rs}}
11+
```
12+
13+
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).
14+
15+
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.
16+
17+
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.
18+
19+
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.
20+
21+
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.)
22+
23+
Finally, we call `.build()` to turn that all into a proper `Capsule`.
24+
25+
## Using widgets
26+
27+
Here's an example of a page that uses some widgets:
28+
29+
```
30+
{{#include ../../../examples/core/capsules/src/templates/index.rs}}
31+
```
32+
33+
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 `()`.
34+
35+
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).
36+
37+
### Widget paths
38+
39+
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.)
40+
41+
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.
42+
43+
if you want to see a more advanced example of a capsule that uses incremental generation to build widgets, check out [this code].
44+
45+
*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].*
46+
47+
## Delayed widgets
48+
49+
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.
50+
51+
*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.*
52+
53+
## Rescheduling
54+
55+
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).
56+
57+
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.
58+
59+
## Support
60+
61+
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.

docs/next/en-US/first-app/deploying.md

+10
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ When it's done, this command wil produce a `pkg/` folder in the root of your pro
3232

3333
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.
3434

35+
### Optimizations
36+
37+
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!)
38+
39+
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.
40+
41+
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.)
42+
43+
For more information on optimizing Wasm bundle sizes, see [here](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size).
44+
3545
## Export deployment
3646

3747
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!

docs/next/en-US/reference/compilation-times.md renamed to docs/next/en-US/fundamentals/compilation-times.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ The easiest way to get Perseus to use this, instead of the usual `cargo`, is to
1414

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

17-
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.)
17+
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.

docs/next/en-US/reference/architecture.md

-1
This file was deleted.

docs/next/en-US/reference/deploying.md

-44
This file was deleted.

0 commit comments

Comments
 (0)