Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
deepio committed Apr 9, 2023
1 parent c362664 commit 4830abe
Show file tree
Hide file tree
Showing 15 changed files with 26 additions and 26 deletions.
4 changes: 2 additions & 2 deletions docs/0.4.x/en-US/first-app/defining.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Defining a Perseus App

Once you've got all your dependencies installed, it's time to create the entrypoint to your Perseus app. In most Rust programs. you'll have a `main.rs` file that contains some `fn main() { .. }` that executes yuor code, and Perseus is no exception. However, remember that Perseus has two parts: the engine-side and the client-side, so you actually need *two* `main()` functions, one for each. Now, don't put anything in `src/main.rs` just yet, because, as we'll see later, there's actually a much more convenient way of handling all this.
Once you've got all your dependencies installed, it's time to create the entrypoint to your Perseus app. In most Rust programs. you'll have a `main.rs` file that contains some `fn main() { .. }` that executes your code, and Perseus is no exception. However, remember that Perseus has two parts: the engine-side and the client-side, so you actually need *two* `main()` functions, one for each. Now, don't put anything in `src/main.rs` just yet, because, as we'll see later, there's actually a much more convenient way of handling all this.

Remember, you can tell Rust to only compile some code on the engine-side by putting `#[cfg(engine)]` over it, and you can use `#[cfg(client)]` to do the same for the browser. So, our code in `main.rs` should logically look something like this:

Expand All @@ -18,7 +18,7 @@ fn main() {

Now, this actually isn't too far off, except that running WebAssembly is a little different than you might think. Currently, there isn't really a good concept of a 'binary' Wasm program, you'll always be coding a library that some JavaScript imports and runs. In the case of Perseus apps, we use a `main.rs` file because it makes more logical sense, since Perseus handles all that nasty JS stuff behind the scenes. From your point of view, you're just writing a normal binary. However, there is something special that the client-side function has to do: it has to return a `Result<(), JsValue>`, where `JsValue` is a special type that represents *stuff* in JS-land. You can use Perseus' [`ClientReturn`](=type.ClientReturn@perseus) type alias for this, but note that Perseus actually *can't* return an error from its invocation: all errors are gracefully handled, even panics (although they will eventually propagate up as an unhandled exception in the calling JS, which is why any panics in Perseus will appear as two messages in your browser console rather than one).

Further, Perseus makes the engine and client code pretty convenient with two features (which are enabled by default): `dflt-engine`, and `client-helpers`. The first of these gives us the [`run_dftl_engine()`](=engine/fn.run_dflt_engine@perseus) fucntion, which takes an [`EngineOperation`](=engine/enum.EngineOperation@perseus) derived from the [`get_op()`](=engine/fn.get_op@perseus) function (which just parses environment variables passed through by the CLI), a function that returns a [`PerseusApp`](=prelude/struct.PerseusAppBase) (whcih we'll get to), and some function to run your server.
Further, Perseus makes the engine and client code pretty convenient with two features (which are enabled by default): `dflt-engine`, and `client-helpers`. The first of these gives us the [`run_dftl_engine()`](=engine/fn.run_dflt_engine@perseus) function, which takes an [`EngineOperation`](=engine/enum.EngineOperation@perseus) derived from the [`get_op()`](=engine/fn.get_op@perseus) function (which just parses environment variables passed through by the CLI), a function that returns a [`PerseusApp`](=prelude/struct.PerseusAppBase) (which we'll get to), and some function to run your server.

As for the client-side, Perseus provides `run_client()`, which just takes a function that returns a `PerseusApp`.

Expand Down
2 changes: 1 addition & 1 deletion docs/0.4.x/en-US/first-app/dev-cycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

When you're developing a Perseus app, you'll generally have two "modes": coding, and fine-tuning. In the *coding* stage, you're building features of your app, which will typically involve quite a lot of working on business logic, etc. If you're familiar with Rust programming, this is the stage when you'd be using `cargo check` instead of `cargo run`. Conveniently, Perseus provides `perseus check -w` for this, which will not only `cargo check` your app's engine-side, but also the browser-side, because each one is built for a different target. This command is *much* faster than `perseus serve`, because it just checks your code, rather than actually compiling it. If you want to test your build logic as well, you can run `perseus check -gw`, which will also test this (but that will take a bit longer).

When you're using an IDE, like VS Code, you'll usually want proper syntax highlighting, and you may find that Perseus causea few problems. This is because Perseus distinguishes between the engine and the browser by using a custom feature, so you'll need to create a `.cargo/config.toml` file in the root of your project with the following contents:
When you're using an IDE, like VS Code, you'll usually want proper syntax highlighting, and you may find that Perseus can cause a few problems. This is because Perseus distinguishes between the engine and the browser by using a custom feature, so you'll need to create a `.cargo/config.toml` file in the root of your project with the following contents:

```toml
[build]
Expand Down
2 changes: 1 addition & 1 deletion docs/0.4.x/en-US/first-app/generating-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Those [`#[engine_only_fn]`](=prelude/attr.engine_only_fn@perseus) macros are ver

You might be wondering about error handling on the engine-side: surely, if you're connecting to a database, you would need to return errors sometimes? What if the server building your app loses its internet connection? Well, you actually can return errors. In fact, try changing the return types of both `head` and `get_build_state` to return `Result<T, std::io::Error>`, where `T` was what they returned before. If you then wrap what they're returning in `Ok(..)`, there will be no errors. Perseus is designed to accept either fallible or infallible functions, and the error type can be whatever you like, as long as it implements `std::error::Error`. For `get_build_state` though, it's actually a tiny bit more complicated than this, as you'll need to wrap your error type in something called [`BlamedError`](prelude/struct.BlamedError@perseus), which you can learn more about in [the section on build-time state generation](:state/build).

And finally, we come to that famous `get_template` function, which we call from `PerseusApp` to get this whole template. This is responsible for producing a [`Template`](prelude/struct.Template@perseus) that strings everything together. This too takes a `G: Html` bound, and the `Template::build("index")` call is setting up a new template whose pages will all fal under `/index`, but `index` is a special name, and it resolves to an empty string. In other words, you're creating the template for the root of your site. Then we declare our build state function, out view function, and our head function. Since we're not actually using our state in the head, we could have used `.head()` instead of `.head_with_state()`, but we showed the state for demonstration purposes. Finally, we call `.build()` to create the full `Template`, which we return.
And finally, we come to that famous `get_template` function, which we call from `PerseusApp` to get this whole template. This is responsible for producing a [`Template`](prelude/struct.Template@perseus) that strings everything together. This too takes a `G: Html` bound, and the `Template::build("index")` call is setting up a new template whose pages will all fall under `/index`, but `index` is a special name, and it resolves to an empty string. In other words, you're creating the template for the root of your site. Then we declare our build state function, out view function, and our head function. Since we're not actually using our state in the head, we could have used `.head()` instead of `.head_with_state()`, but we showed the state for demonstration purposes. Finally, we call `.build()` to create the full `Template`, which we return.

This is called the *functional definition* pattern in Perseus: you define your `Template`s inside functions (usually called `get_template()`), which you then call in `PerseusApp`.

Expand Down
2 changes: 1 addition & 1 deletion docs/0.4.x/en-US/first-app/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Next, put the following in your app's `Cargo.toml`:
{{#include ../../../examples/core/basic/Cargo.toml.example}}
```

The main things to pay attention to here are the dependencies, which are laid out differently from most Rust apps. Perseus is built in two parts: the *engine-side*, which is responsible for prerendering your pages, serving content, exporting your app, etc.; and the *client-side*, which runs inside a user's browser to make Perseus interactive, handling routing, interactivity, etc. The engine-side of your app will build to whatever target you compile it for, like `x86_64-unknown-linux-gnu`, which you would have on an OS like Ubuntu. This means Rust will translate your code into machine code that computers with that kind of processor and OS can understand (if you were running on an M1 Mac, the target would be quite different). The browser has its own sepaarate target, which ensures that you don't have to compile your code for every possible device that a user might view it on --- the browser takes care of all that, and runs Wasm, which is its own special language that Rust can translate itself into.
The main things to pay attention to here are the dependencies, which are laid out differently from most Rust apps. Perseus is built in two parts: the *engine-side*, which is responsible for prerendering your pages, serving content, exporting your app, etc.; and the *client-side*, which runs inside a user's browser to make Perseus interactive, handling routing, interactivity, etc. The engine-side of your app will build to whatever target you compile it for, like `x86_64-unknown-linux-gnu`, which you would have on an OS like Ubuntu. This means Rust will translate your code into machine code that computers with that kind of processor and OS can understand (if you were running on an M1 Mac, the target would be quite different). The browser has its own separate target, which ensures that you don't have to compile your code for every possible device that a user might view it on --- the browser takes care of all that, and runs Wasm, which is its own special language that Rust can translate itself into.

That all means that there are some features that don't belong in the browser (like building your app), and others that don't belong in the engine (like managing routing), so Perseus *target-gates* these, using Rust's `#[cfg(..)]` macro to make sure that certain things are only compiled at the right time. This reduces compilation times, and also slims down the bundles for both the engine and the browser (because they contain no unnecessary code). Sometimes, you'll want to do this in your own code as well, like if you have some function that should only run on the browser-side. Remember how we set up that `rustflags` key in `.cargo/config.toml`? Well, that's so you can use it just like this! If you want code to only be compiled for the browser, you put `#[cfg(client)]` on top of it, and you can use `#[cfg(engine)]` to do the same for the engine. You'll usually see this in Rust code, but your `Cargo.toml` can use it too for declaring dependencies that will only be used on one particular target. Here, we're making sure to bring in `perseus` everywhere, but `perseus-warp` (our server integration) should only be used on the engine-side. When you bring in a new dependency, think about whether it has to be available on the browser-side, because it often doesn't. For example, you could bring in the `regex` crate to automatically highlight any technical terms in a documentation site, but you can actually do that solely on the engine-side if you handle all that in the state generation process (which we'll get to). This avoids bringing the `regex` crate into the browser, which keeps your `.wasm` bundle nice and slim. A smaller Wasm bundle means it can be transferred over the network more quickly, which means faster page loads.

Expand Down
2 changes: 1 addition & 1 deletion docs/0.4.x/en-US/fundamentals/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ One of the most powerful features of Perseus is its extensibility, which comes i

However, 99.99% of the time, you won't need to do any of this, because your needs will be met far more effectively by either a [custom server](:fundamentals/serving-exporting), or a plugin.

Plugins in Perseus are library crates, usually published on crates.io, that can be used as dependencies in your app that have access to various *plugin opportunities*, whcih are basically points in your app where Perseus allows third-party code to do certain things.
Plugins in Perseus are library crates, usually published on crates.io, that can be used as dependencies in your app that have access to various *plugin opportunities*, which are basically points in your app where Perseus allows third-party code to do certain things.

Plugins fall into two types: *functional* and *control*. Functional plugins are very simple: they're given some data at a certain time, they do some stuff, and then they return some data. For a single plugin opportunity, there can be as many functional plugins registered as you like. For example, a plugin can define extra static aliases, and, of course, the results of many plugins doing this can all be collated together. Control plugins work differently: for a single control plugin opportunity, only one plugins can act, for example redefining the index view (since you can't necessarily combine two completely different index views from two completely different plugins).

Expand Down
2 changes: 1 addition & 1 deletion docs/0.4.x/en-US/fundamentals/preloading.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

One superpower of Perseus is its caching system, which takes any pages the user has already been to, figures out the minimium amount of information necessary to restore them without any network requests, and stores that, ensuring that pressing the back button leads to an instant response. Sometimes, however, you want this to work in the other direction too: if you are fairly confident of which page a user will go to next, you can *preload* it to make sure they get the content immediately.

Now, usually you would do preloading through the browser, whcih will fetch resources intelligently to minimize load times, but, again, Perseus knows better than the browser in a lot of cases. To render a new page, all it needs is the page's state and its document metadata, which actually come from a special internal link (behind `/.perseus/page`). Preloading this through the browser is finicky, and it doesn't allow Perseus to do some pre-parsing to keep things speedy, so Perseus provides its own imperative preloading interface.
Now, usually you would do preloading through the browser, which will fetch resources intelligently to minimize load times, but, again, Perseus knows better than the browser in a lot of cases. To render a new page, all it needs is the page's state and its document metadata, which actually come from a special internal link (behind `/.perseus/page`). Preloading this through the browser is finicky, and it doesn't allow Perseus to do some pre-parsing to keep things speedy, so Perseus provides its own imperative preloading interface.

There are two ways of using this interface: there's the easy way, and the fine-grained way. The easy way is to use the `.preload()` method on the `Reactor`, which spawns a future for you and panics on errors that you caused (like a misspelled route), while silently failing on errors from the server. Alternately, you could use the `.try_preload()` method, which lets you handle the errors, and forces you to manage the asynchronicity yourself. If you want more control over the error handling (which applies especially if you're preloading a route that you haven't hardcoded), then you should use this method instead.

Expand Down
Loading

0 comments on commit 4830abe

Please sign in to comment.