diff --git a/docs/next/en-US/features.md b/docs/next/en-US/features.md index 60fe3a4550..a6cf5bd85d 100644 --- a/docs/next/en-US/features.md +++ b/docs/next/en-US/features.md @@ -1,6 +1,6 @@ # Feature Discovery Terminal -The rest of these docs are written in a bit of an experimental way. Perseus is *really big*, and there are a lot of features that you can use. This is a place where you can learn about them all, and easily find code examples for a particular pattern you want to use. Perseus is written with a ton of a examples, and these are all great sources of information, but they're a bit hard to find sometimes. This page is a list of usage patterns of Perseus that you can use to fidn exactly what you're looking for! +The rest of these docs are written in a bit of an experimental way. Perseus is *really big*, and there are a lot of features that you can use. This is a place where you can learn about them all, and easily find code examples for a particular pattern you want to use. Perseus is written with a ton of a examples, and these are all great sources of information, but they're a bit hard to find sometimes. This page is a list of usage patterns of Perseus that you can use to find exactly what you're looking for! *Note: this is a bit of an experimental documentation design. Let us know what you think and add your suggestions [here](https://github.com/arctic-hen7/perseus/discussions/new)!* diff --git a/docs/next/en-US/getting-started/first-app.md b/docs/next/en-US/getting-started/first-app.md index 905b5be297..3d1d0cf23d 100644 --- a/docs/next/en-US/getting-started/first-app.md +++ b/docs/next/en-US/getting-started/first-app.md @@ -17,7 +17,7 @@ These two environments couldn't really be more different, and, while Perseus tri The first section is the usual one, pulling in dependencies that we want everywhere. Both `perseus` and `sycamore` are needed in the browser and on the server-side, so we put them here. Most of the packages you bring in yourself will go here too. As a general rule of thumb, put stuff here unless you're getting errors or trying to optimize your app (which we have a whole section on). -The second is `[target.'cfg(not(target_arch = "wasm32"))'.dependencies]`, which looks scary, but is actually pretty simple when you think about it. It defines the `dependencies` section only on the `target` (operating system) that matches the condition `cfg(not(target_arch = "wasm32"))` --- the target that's not `wasm32`, which is Rust's way of talking about the browser. This section contains engine-only dependencies. In other words, the code that runs in the browser will have no clude these even exist. We put two things in here: `tokio` and `perseus-warp`. The first is an asynchronous runtime that Perseus uses in the background (this means we can do stuff like compile three parts of your app at the same time, which speeds up builds). The second is one of those integration crates we were talking about earlier, with the `dflt-server` feature enabled, which makes it expose an extra function that just makes us a server that we don't have to think about. Unless you're writing custom API routes, this is all you need. +The second is `[target.'cfg(not(target_arch = "wasm32"))'.dependencies]`, which looks scary, but is actually pretty simple when you think about it. It defines the `dependencies` section only on the `target` (operating system) that matches the condition `cfg(not(target_arch = "wasm32"))` --- the target that's not `wasm32`, which is Rust's way of talking about the browser. This section contains engine-only dependencies. In other words, the code that runs in the browser will have no clue these even exist. We put two things in here: `tokio` and `perseus-warp`. The first is an asynchronous runtime that Perseus uses in the background (this means we can do stuff like compile three parts of your app at the same time, which speeds up builds). The second is one of those integration crates we were talking about earlier, with the `dflt-server` feature enabled, which makes it expose an extra function that just makes us a server that we don't have to think about. Unless you're writing custom API routes, this is all you need. The third section is exactly the same as the previous, just without that `not(...)`, so this one defines dependencies that we use in the browser only. We've put `wasm-bindgen` here, which we could compile on the server, but it would be a little pointless, since Perseus only uses it behind the scenes in making your app work in the browser. (This is needed for a macro that a Perseus macro defines, which is why you have to import it yourself.) diff --git a/docs/next/en-US/reference/compilation-times.md b/docs/next/en-US/reference/compilation-times.md index 8fe6256b82..3ff1972d5a 100644 --- a/docs/next/en-US/reference/compilation-times.md +++ b/docs/next/en-US/reference/compilation-times.md @@ -4,7 +4,7 @@ As you may have noticed while using Perseus, compiling even very small changes t The first step in addressing this is to figure out whether your app needs exporting or not. If not, then you should use `#[perseus::main_export]` rather than `#[perseus::main(...)]` in your `lib.rs`, and then you don't need to bring in a server integration, which should speed things up slightly. Notably, if you choose not to do this, there's no actual compile-time difference between `perseus export` and `perseus serve` (there used to be, back in the days of `.perseus/`, but those horrors are gone now). As a general rule, apps that *can* go to static files *should* go to static files, because they'll be faster. (It's also more easily possible to push static files to edge networks in deployment, which usually means faster load times for your users, depending on your hosting provider.) -The next step is pretty trivial: `rustup override set nightly`. This command will set the default toolchain in your project (you have to run that command in your proejct directory) to `nightly`, which tends to nearly cut compile times in half! In general, the nightly compiler will be much faster than the stable one, because it uses all sorts of unstable features that improve compilation times. If you want maximum assurances though, switch back to the stable compiler before running `perseus deploy` so your production app is secured against any nightly compiler bugs (which do happen) and you get the best of both worlds. +The next step is pretty trivial: `rustup override set nightly`. This command will set the default toolchain in your project (you have to run that command in your project directory) to `nightly`, which tends to nearly cut compile times in half! In general, the nightly compiler will be much faster than the stable one, because it uses all sorts of unstable features that improve compilation times. If you want maximum assurances though, switch back to the stable compiler before running `perseus deploy` so your production app is secured against any nightly compiler bugs (which do happen) and you get the best of both worlds. From here, we get a little more radical. There's something called [Cranelift](), a compiler backend that can be used [for Rust]() to speed up development compilation times. Of course, the sacrifice here is runtime speed, but as long as you build with the usual compiler for production, you again get the best of both worlds. To set this up, you'll need to download the latest version of [this project](https://github.com/bjorn3/rustc_codegen_cranelift) from [here](https://github.com/bjorn3/rustc_codegen_cranelift/actions?query=branch%3Amaster+event%3Apush+is%3Asuccess) (click on the most recent action run, and then download the appropriate artifact). Then, unzip that (twice, there's a nested zip folder) and you'll get a `build/` folder, which you can put anywhere on your system. Then, add the contents of that (which should contain `cargo-clif` and `rustc-clif`) to your system's `PATH` (tutorial [for Windows](https://stackoverflow.com/a/44272417), and [for Linux/MacOS](https://linuxize.com/post/how-to-add-directory-to-path-in-linux/)). diff --git a/docs/next/en-US/reference/deploying.md b/docs/next/en-US/reference/deploying.md index 307f89ba2b..751a53ad25 100644 --- a/docs/next/en-US/reference/deploying.md +++ b/docs/next/en-US/reference/deploying.md @@ -8,7 +8,7 @@ If you don't need a server for your app, you can use `perseus deploy -e`, which ## Optimizations -Of course, when you're deploying your app, you want it to be as fast as possible. On the engine-side, this is handled automatically by Rust, which will naturally produce super-fast binaries. On the browser-side, there are problems though. This is because of the way the internet works --- before your users can run your super-fast code, they need to download it first. That download process is what's involved in loading your app, which is generally the indicator of speed on the web. That means we actually improve the speed of your app by optimizing more aggreassively for the *size* of your app, thus minimizing download times and making your app load faster. +Of course, when you're deploying your app, you want it to be as fast as possible. On the engine-side, this is handled automatically by Rust, which will naturally produce super-fast binaries. On the browser-side, there are problems though. This is because of the way the internet works --- before your users can run your super-fast code, they need to download it first. That download process is what's involved in loading your app, which is generally the indicator of speed on the web. That means we actually improve the speed of your app by optimizing more aggressively for the *size* of your app, thus minimizing download times and making your app load faster. With JavaScript, you can 'chunk' your app into many different files that are loaded at the appropriate times, but no such mechanisms exists yet for Wasm of any kind, which means your final `bundle.wasm` will be big. This is often used as a criticism of Wasm: the Perseus basic example produces a bundle that's over 200kb, where a JavaScript equivalent would be a tenth of the size. However, this comparison is flawed, since JavaScript is actually slower to execute. It's an oversimplification, but you can think of it like this: JS needs to be 'compiled' in the browser, whereas Wasm is already compiled. For that reason, it's better to compare Wasm file sizes to image file sizes (another type of file that doesn't need as much browser processing). In fact, that over 200kb bundle is probably faster than the tenth-of-the-size JS. diff --git a/docs/next/en-US/reference/faq.md b/docs/next/en-US/reference/faq.md index 9b4f90b498..841701824a 100644 --- a/docs/next/en-US/reference/faq.md +++ b/docs/next/en-US/reference/faq.md @@ -25,7 +25,7 @@ That will archive the `git/` and `registry/` folders in `~/.cargo/`, which shoul If you've tried to download the bleeding-edge version of the CLI with `cargo install`, using a Git dependency on the `perseus-cli` package, you've probably been hit with a whole host of errors that don't make a whole lot of sense! The reason for this is that the Perseus CLI depends on including a folder that's not checked into Git (the engine, `.perseus/`, but transformed in various ways). That means that, to build the CLI, you need to have that folder available, which `cargo install` isn't smart enough to do yet from a Git dependency. -The way you get around this is unfortunately inconvenient, you'll have to manually clone the whole Perseus repository and then build the CLI yourself. You can do that by running `bonnie setup` in the root of the Perseus repo (after you've cloned it), and then you can build the binary in `packages/perseus-cli/` -- that will give you a copy of the CLI in `target/`! Be warned though that ussing the bleeding-edge CLI is generally not recommended, as the interdependencies in the engine can be quite fragile, and even the smallest changes that aren't breaking usually can be breaking when you're using the bleeding-edge version of the CLI with a released version of Perseus. +The way you get around this is unfortunately inconvenient, you'll have to manually clone the whole Perseus repository and then build the CLI yourself. You can do that by running `bonnie setup` in the root of the Perseus repo (after you've cloned it), and then you can build the binary in `packages/perseus-cli/` -- that will give you a copy of the CLI in `target/`! Be warned though that using the bleeding-edge CLI is generally not recommended, as the interdependencies in the engine can be quite fragile, and even the smallest changes that aren't breaking usually can be breaking when you're using the bleeding-edge version of the CLI with a released version of Perseus. ## Hydration doesn't work with X diff --git a/docs/next/en-US/reference/initial_subsequent_loads.md b/docs/next/en-US/reference/initial_subsequent_loads.md index 0341b1ad7d..f117af1458 100644 --- a/docs/next/en-US/reference/initial_subsequent_loads.md +++ b/docs/next/en-US/reference/initial_subsequent_loads.md @@ -1,6 +1,6 @@ # Initial vs. Subsequent Loads -In a Persues app, there are two ways for a page in your app to be loaded, and it's important to understand this if you want to work with the more advanced features of Perseus. The first way is an *initial load*, which is when a user comes onto your site from an external URL (e.g. a search engine). The second is a *subsequent load*, which is when a user moves from one page on your site to another (e.g. a link on your landing page takes them to an about page). +In a Perseus app, there are two ways for a page in your app to be loaded, and it's important to understand this if you want to work with the more advanced features of Perseus. The first way is an *initial load*, which is when a user comes onto your site from an external URL (e.g. a search engine). The second is a *subsequent load*, which is when a user moves from one page on your site to another (e.g. a link on your landing page takes them to an about page). ## Initial Loads @@ -12,7 +12,7 @@ This HTML file has in it a prerendered version of the page, meaning the user wil Once this Wasm is loaded, all other links in the app are controlled by subsequent loads. Importantly, if the user goes to an external URL and then comes back, another initial load will occur (though hopefully their browser will have cached the Wasm bundle, reducing the load time to almost zero). -One caveat to all this is if i18n is being used, in which case there's unfortunately no way for the server to reliably know which language a page should be returned in. If the user requested `/en-US/about`, no problem, but if they just gave us `/about`, we need to send them a script to figure out their locale. Specifically, this comes in a blank HTML page that includes the Wasm bundle, which will then detect the locale and mvoe ahead with rendering. +One caveat to all this is if i18n is being used, in which case there's unfortunately no way for the server to reliably know which language a page should be returned in. If the user requested `/en-US/about`, no problem, but if they just gave us `/about`, we need to send them a script to figure out their locale. Specifically, this comes in a blank HTML page that includes the Wasm bundle, which will then detect the locale and move ahead with rendering. Unfortunately, this approach does lead to a moment of having a blank screen before the Wasm bundle has loaded, something that we aim to resolve in the longer-term. diff --git a/docs/next/en-US/reference/plugins.md b/docs/next/en-US/reference/plugins.md index 070bebaebf..4d3b30fd2e 100644 --- a/docs/next/en-US/reference/plugins.md +++ b/docs/next/en-US/reference/plugins.md @@ -1,6 +1,6 @@ # Plugins -Like many fullstack frameworks, Perseus supports *plugins*, which allow you to extend the basic functionality of Perseus in sometimes extreme ways! However, unlike other frameworks, Perseus is already extremely customizabel with usual usage, due to the way it exposes all operations directly to the user. For example, if you wanted to restructure your server, all that code is open to you directly. +Like many fullstack frameworks, Perseus supports *plugins*, which allow you to extend the basic functionality of Perseus in sometimes extreme ways! However, unlike other frameworks, Perseus is already extremely customizable with usual usage, due to the way it exposes all operations directly to the user. For example, if you wanted to restructure your server, all that code is open to you directly. In earlier version of Perseus, there was a folder called `.perseus/` that stored a large amount of internal code, and plugins were mostly used to modify that. Today, that code simply doesn't exist anymore, and everything is bundled into the main app! (With the consequence of a slightly wild `Cargo.toml`...) This means that most things aren't done with a plugin anymore. diff --git a/docs/next/en-US/reference/state-generation.md b/docs/next/en-US/reference/state-generation.md index 8918777707..b86f14213a 100644 --- a/docs/next/en-US/reference/state-generation.md +++ b/docs/next/en-US/reference/state-generation.md @@ -6,7 +6,7 @@ One of the most important features of Perseus is its state platform, as explaine The *build state* strategy is very simple: provide a function to a [`Template`](=struct.Template@perseus) and it will be run when you build your app. Its output will then be used as state! -To take a simple example, let's say you have a (very contrived) page that should display the number of entries in a database at the time it was built. You would make a templat for this, perhaps called `entries` (so it would be hosted at `/entries` on your site), and you'd provide a simple view that calls on some state, which would only need a single property for the number of entries in the DB. Then, just show that! +To take a simple example, let's say you have a (very contrived) page that should display the number of entries in a database at the time it was built. You would make a template for this, perhaps called `entries` (so it would be hosted at `/entries` on your site), and you'd provide a simple view that calls on some state, which would only need a single property for the number of entries in the DB. Then, just show that! To make this work though, we need to execute some logic at build-time, which we can do with *build state*! By calling the `.build_state_fn()` method on [`Template`](struct.Template@perseus) and providing a function annotated with `#[perseus::build_state]`, that function will be called when the template is built, and its output will be used to provide the initial value of the state. @@ -18,7 +18,7 @@ A *build state* function takes two parameters: the path (see below) and the loca But now, let's say we actually have multiple tables in our database, and we want to know how many entries are in each one, with each count being displayed on a different page. Really, we want several pages under that `/entries` path now. This is easily achievable with *build paths*, which allows a single template to generate many pages. -By providing a function to the `.build_paths_fn()` method of [`Template`](=struct.Template@perseus), that will be called at build-time to generate a `Vec` of paths underneath `/entries`. For instant, if we returned `vec![ "foo".to_string(), "bar".to_string(), "baz".to_string() ]`, Perseus would create pages at `/entries/foo`, `/entires/bar`, and `/entries/baz`. If you wanted to create an `/entries` page, you would provide an empty string as one of the elemtents in that `Vec`. +By providing a function to the `.build_paths_fn()` method of [`Template`](=struct.Template@perseus), that will be called at build-time to generate a `Vec` of paths underneath `/entries`. For instant, if we returned `vec![ "foo".to_string(), "bar".to_string(), "baz".to_string() ]`, Perseus would create pages at `/entries/foo`, `/entires/bar`, and `/entries/baz`. If you wanted to create an `/entries` page, you would provide an empty string as one of the elements in that `Vec`. Note that you can also create nested paths like `foo/bar/baz` just like that. @@ -42,7 +42,7 @@ A *request state* function takes three arguments: the path, the locale it's bein However, there's a problem with the above idea in most frameworks that support build state and request state, or similar principles. You can only usually use one, since otherwise the build state and the request state might generate conflicting states! This is exactly what would happen here: the build state would happily get the count, and the request state would always override this as `None`, authorized or not, and it would set `authorized`, which the build state might always assume to be `true`. Whatever shall we do? -The answer to this is dead simple: the *state amalgamation* strategy, whjich allows us to take in both of those states and do some arbitrary stuff to resolve them. In this case, based on the value of `authorized` property from the *request state*, we would either return `authorized: false, state: None`, or `authorized: true, state: Some(state)`, where `state` in the latter comes from the *build state* function. Nifty, eh? +The answer to this is dead simple: the *state amalgamation* strategy, which allows us to take in both of those states and do some arbitrary stuff to resolve them. In this case, based on the value of `authorized` property from the *request state*, we would either return `authorized: false, state: None`, or `authorized: true, state: Some(state)`, where `state` in the latter comes from the *build state* function. Nifty, eh? Note though that you won't always need state amalgamation, it's mostly useful for adding this kind of authentication to pages that already have build state, allowing you to get the best optimizations and the best security! @@ -74,4 +74,4 @@ And *this* is why you the *build state* function returns a [`RenderFnResultWithC ## Examples -Some of this may be a little tricky to visualize, so there's an example [here](https://github.com/artic-hen7/perseus/tree/main/examples/core/state_generation) that goes through each of Perseus' state generation strategies systemtically! Note that it doesn't use the same example of a database entry counter as described here, but rather more basic examples to just show the basic functionality of each strategy. Enjoy! +Some of this may be a little tricky to visualize, so there's an example [here](https://github.com/artic-hen7/perseus/tree/main/examples/core/state_generation) that goes through each of Perseus' state generation strategies systematically! Note that it doesn't use the same example of a database entry counter as described here, but rather more basic examples to just show the basic functionality of each strategy. Enjoy!