Skip to content

Commit

Permalink
docs: wrote first app tutorial
Browse files Browse the repository at this point in the history
A lot of missing links right now!
  • Loading branch information
arctic-hen7 committed Jan 10, 2023
1 parent 520eebf commit 149a521
Show file tree
Hide file tree
Showing 11 changed files with 526 additions and 163 deletions.
67 changes: 50 additions & 17 deletions docs/next/en-US/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,56 @@
# Introduction

- [Introduction](/docs/intro)
- [Quickstart](/docs/quickstart)
- [What is Perseus?](/docs/what-is-perseus)
- [Core Principles](/docs/core-principles)

# Reference

- [Feature Discovery Terminal](/docs/features)
- [Improving Compilation Times](/docs/reference/compilation-times)
- [State Platform](/docs/reference/state-platform)
- [State Generation](/docs/reference/state-generation)
- [Live Reloading and HSR](/docs/reference/live-reloading-and-hsr)
- [Internationalization](/docs/reference/i18n)
- [Hydration](/docs/reference/hydration)
- [Static Exporting](/docs/reference/exporting)
- [Plugins](/docs/reference/plugins)
- [Deploying](/docs/reference/deploying)
- [Architecture Details](/docs/reference/architecture)
- [Router](/docs/reference/router)
- [Initial vs. Subsequent Loads](/docs/reference/initial_subsequent_loads)
- [Migrating from v0.3.x](/docs/reference/migrating)
- [Common Pitfalls and Known Bugs](/docs/reference/faq)
# Your First App

- [Installing Perseus](/docs/first-app/installation)
- [Defining your app](/docs/first-app/defining)
- [Generating pages](/docs/first-app/generating-pages)
- [Development cycle](/docs/first-app/dev-cycle)
- [Error handling](/docs/first-app/error-handling)
- [Deploying your app](/docs/first-app/deploying)

# Fundamentals

- [`PerseusApp`](/docs/fundamentals/perseus-app)
- [Routing and navigation](/docs/fundamentals/routing)
- [Preloading](/docs/fundamentals/preloading)
- [Internationalization](/docs/fundamentals/i18n)
- [Error views](/docs/fundamentals/error-views)
- [Hydration](/docs/fundamentals/hydration)
- [Static content](/docs/fundamentals/static-content)
- [Styling](/docs/fundamentals/styling)
- [Working with JS](/docs/fundamentals/js-interop)
- [Servers and exporting](/docs/fundamentals/serving-exporting)
- [Debugging](/docs/fundamentals/debugging)
- [Writing tests](/docs/fundamentals/testing)
- [Plugins](/docs/fundamentals/plugins)
- [Improving Compilation Times](/docs/fundamentals/compilation-times)

# The State Platform

- [Understanding state](/docs/state/intro)
- [Build-time state](/docs/state/build)
- [Request-time state](/docs/state/request)
- [Revalidation](/docs/state/revalidation)
- [Incremental generation](/docs/state/incremental)
- [Using state](/docs/state/browser)
- [Global state](/docs/state/global)
- [Helper state](/docs/state/helper)
- [Suspended state](/docs/state/suspense)
- [Freezing and thawing](/docs/state/freezing-thawing)

# Capsules

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

# Miscellaneous

- [Migrating from v0.3.x](/docs/migrating)
- [Common pitfalls and FAQs](/docs/faq)
146 changes: 0 additions & 146 deletions docs/next/en-US/features.md

This file was deleted.

51 changes: 51 additions & 0 deletions docs/next/en-US/first-app/defining.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 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.

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:

```
#[cfg(engine)]
fn main() {
// Engine code here
}
#[cfg(client)]
fn main() {
// Browser code here
}
```

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.

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

So what is this `PerseusApp`, you might ask? This `struct` forms the bridge between Perseus' internals, and your own code, because it's how you tell Perseus what your app looks like. In fact, because the vast majority of engine and client `main()` functions are so formulaic, Perseus provides a convenient macro, [`#[perseus::main(..)]`](=attr.main@perseus), which you can use to annotate a *single* `main()` function that returns a `PerseusApp`, and that macro will then do the rest automatically. Most of time, this is what you want, but you can always take a look at [the source code]() of that macro if you want to drill deeper into customizing your app (again, you will probably *never* need to do this, even if you're creating an insanely advanced app).

So, our actual `src/main.rs` file would look something like this (theory over, *now* we start coding):

```
{{#include ../../../examples/core/basic/src/main.rs}}
```

First off, we declare a module called `templates`, which will correspond to the `src/templates/` folder, which we'll use to store the code for all our templates. Go ahead and create that folder now, with an empty `mod.rs` file inside. The next thing is to import the Perseus `prelude` module, which just collates everything you'll need to run a Perseus app, which helps to avoid having to manually import a million different things. Most of your Perseus files will begin with `use perseus::prelude::*;`, and then `use sycamore::prelude::*;`

Then we get to that special `main()` function. As you can see, it returns a `PerseusApp`, which takes a generic `G`: this is a special part of Sycamore that lets is say "let this function work with any rendering backend that implements `Html`", because Sycamore can actually go way beyond the web! This generic restricts us to using `SsrNode` (for prerendering), `DomNode` (for rendering to the Document Object Model in the browser), or `HydrateNode` (the same as `DomNode`, but for when we're [hydrating]).

You'll also notice that we've provided an argument to the `#[perseus::main(..)]` attribute macro: that's the function that will start up our server! If you want to add things like custom API routes, etc., then you can set this function manually, and then use one of the Perseus server integrations to work with the code you've written (see [this example] for more), but here we're just using the default server from the `perseus-warp` package. If you fancy [Axum], you can use `perseus-axum`, and [Actix Web] fans can use the `perseus-actix-web` package!

## Your `PerseusApp`

Now we get to the fun stuff: actually defining your app! The first step is to invoke `PerseusApp::new()`, which is what you'll nearly always want, unless you're in an environment with very special characteristics (e.g. a serverless function with a read-only filesystem), or if you want to manage your translations in a non-standard way for internationalization. Again, 99% of the time, `PerseusApp::new()` is fine.

The next thing we do is declare our templates, which we'll create in a moment. Generally, in Perseus, you'll have an `src/templates/` folder that contains all your templates, and each template will export a `get_template()` function that you call from here. However, if you're from JS-land, where you might be used to something called *filesystem routing* (in which the nesting of a file implies the route it will be hosted at), Perseus has no such thing. If you want to store the about page at `index.rs` and the index page at `about.rs`, have fun!

The next thing we do is specify some [`ErrorViews`](=error_views/struct.ErrorViews@perseus), which are responsible for doing all the error handling in our app. We'll cover this in more detail in [the error handling section](:first-app/error-handling), but just know for now that Perseus has a very strict error handling system, and, unlike a lot of other frameworks, there is no such thing as an unhandled error in Perseus: *everything* is handled (even panics, though they're a bit special).

Of course, you usually just want to dive straight into your app, so you can leave the `.error_views()` bit out if you like, and Perseus will provide some sensible defaults while you're still in development. However, if you try to deploy your app with those defaults, you'll get errors.

(Note that you might see `ErrorViews::unlocalized_development_defaults()` hanging around a lot in the examples, which basically tells Perseus to force-use those 'sensible defaults' in production as well. This is very convenient for examples about how to use Perseus, but it's almost certainly a bad idea in your own code, especially if you want your app available in multiple languages!)

With all that explained, it's time to create some pages!
43 changes: 43 additions & 0 deletions docs/next/en-US/first-app/deploying.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Deploying!

Congratulations on making it through the tutorial, it's time to deploy your app! First, though, we haven't actually run it yet, so we may as well make sure it all compiles. Remember, you an always do this quickly with `perseus check`, which should give all ticks if everything's working. If not, you've probably just mistyped a variable name or something, which happens to us all. If you're having trouble, let us know [in a GitHub discussion], or [on Discord], and we'll be happy to help! (And remember, there are no stupid questions or dumb bugs.)

When you're ready to actually run your app, you can run `perseus serve`, which will prepare everything to be run for development. When it's ready, you'll be able to see your brand-new app at <http://localhost:8080>, in all its *Hello World!* glory! If you try clicking on the link to the *About* page, you should find that the page doesn't seem to change from the browser's perspective, it just instantly updates: this is Perseus' router in action. Press the back button in your browser to pop back to the landing page, and, again, it should be near-instant (Perseus has *cached* the index page, and can return to it with no network requests needed).

<details>

<summary>I'm throttling my network connection, and Perseus seems extremely slow...</summary>

A lot of DevTools in browsers have the option to throttle your network connection, to emulate how long it would take to load a real app. If you do this with Perseus, however, it will probably take around a full minute to even load your app. You'll see content very quickly because of Perseus' preloading system, but the `bundle.wasm` file will take forever. This is because, in development, Wasm bundles are *huge*. What will optimize and compress down to the size of a small cat photo can start as a muilti-megabyte behemoth, and this is why it's usually not a good idea to throttle Perseus apps to test their load-speed. If you wait for the Wasm bundle to load though, and *then* throttle, you'll get a better idea of real-world performance (if your browser supports this).

</details>

If that's all working, you might want to try going to <http://localhost:8080/foo>, which is a non-existent page. You should see a lovely *Page not found* message, and that's error handling in action!

## Deploying

But enough development shenanigans, we want to deploy this thing! To deploy a Perseus app, you'll need to make sure you've defined your [error views](:first-app/error-handling), because Perseus won't let you use the default implied error views in production.

When you're ready, just run this command:

```
perseus deploy
```

It's literally that easy. And, because Rust is a really nice programming language, something that works in development is basically guaranteed to work in production.

Note that this command will take a rather long time, especially on older machines, because it's applying aggressive optimizations to everything to keep bundle sizes down and page loads speedy, while also trying to keep your app as fast as possible. All these optimizations are configurable, but the defaults are tuned to be sensible, and the `deploy` command does pretty much everything automatically. Usually, there's no post-processing to be done at all.

When it's done, this command wil produce a `pkg/` folder in the root of your project that you can send to any server under the sun. Just tell it to run the `pkg/server` binary, and your app will run beautifully! (But make sure you don't tamper with the contents of this folder, because Perseus needs everything to be in just the right place, otherwise we get one of those crash-and-burn-in-production scenarios.) In fact, try running that binary right now, and you should see your app on <http://localhost:8080> once more!

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.

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

## Closing words

With all that, you've successfully built and deployed your first ever Perseus app: well done! If you're liking Perseus so far, you can check out the rest of the documentation to learn about its features in greater detail, and [the examples] will be your friends in writing real-world Perseus code: they have examples of every single Perseus feature. If you think you've found a bug, please let us know by [opening an issue], or, if you'd like to contribute a feature, you can [open a pull request]. If you're having trouble, you can [open a GitHub discussion] or [as on our Discord], and our friendly community will be happy to help yout out! Also, make sure to take a look at [the Sycamore docs] to learn more about the library that underlies all of Perseus.

Best of luck in your journey, and, if you [defeat Medusa](https://en.wikipedia.org/wiki/Perseus), let us know!
Loading

0 comments on commit 149a521

Please sign in to comment.