diff --git a/docs/next/en-US/hello-world.md b/docs/next/en-US/hello-world.md index c6a1c53bdf..58c3e76505 100644 --- a/docs/next/en-US/hello-world.md +++ b/docs/next/en-US/hello-world.md @@ -52,12 +52,11 @@ Now, create a new directory called `src` and add a new file inside called `lib.r First, we import some things that'll be useful: - `perseus::{define_app, ErrorPages, Template}` -- the -`define_app!` macro, which tells Perseus how your app works; the `ErrorPages` `struct`, which lets you tell Perseus how to handle errors (like _404 Not Found_ if the user goes to a nonexistent page); and the `Template` `struct`, which is how Perseus manages pages in your app -- `std::rc::Rc` -- a [reference-counted smart pointer](https://doc.rust-lang.org/std/rc/struct.Rc.html) (you don't _have_ to understand these to use Perseus, but reading that link would be helpful) -- `sycamore::template` -- Sycamore's [`template!` macro], which lets you write HTML-like code in Rust +- `sycamore::view` -- Sycamore's `view!` macro, which lets you write HTML-like code in Rust -Then, we use the `define_app!` macro to declare the different aspects of the app, starting with the _templates_. We only have one template, which we've called `index` (a special name that makes it render at the root of your app), and then we define how that should look, creating a paragraph (`p`) containing the text `Hello World!`. Perseus does all kinds of clever stuff with this under the hood, and we put it in an `Rc` to enable that. +Then, we use the `define_app!` macro to declare the different aspects of the app, starting with the _templates_. We only have one template, which we've called `index` (a special name that makes it render at the root of your app), and then we define how that should look, creating a paragraph (`p`) containing the text `Hello World!`. -Finally, we tell Perseus what to do if something in your app fails, like if the user goes to a page that doesn't exist. This requires creating a new instance of `ErrorPages`, which is a `struct` that lets you define a separate error page for every [HTTP status code](https://httpstatuses.com), as well as a fallback. Here, we've just defined the fallback. That page is given the URL that caused the error, the HTTP status code, and the actual error message, all of which we display with a Sycamore `template!`, with seamless interpolation. +Finally, we tell Perseus what to do if something in your app fails, like if the user goes to a page that doesn't exist. This requires creating a new instance of `ErrorPages`, which is a `struct` that lets you define a separate error page for every [HTTP status code](https://httpstatuses.com), as well as a fallback. Here, we've just defined the fallback. That page is given the URL that caused the error, the HTTP status code, and the actual error message, all of which we display with a Sycamore `view!`, with seamless interpolation. diff --git a/docs/next/en-US/second-app.md b/docs/next/en-US/second-app.md index c9c9f63410..1f95e34ca8 100644 --- a/docs/next/en-US/second-app.md +++ b/docs/next/en-US/second-app.md @@ -51,7 +51,7 @@ Before we get to the cool part of building the actual pages of the app, we shoul This is a little more advanced than the last time we did this, and there are a few things we should note. -The first is the import of `GenericNode`, which we define as a type parameter on the `get_error_pages` function. As we said before, this means your error pages will work on the client or the server, and they're needed in both environments. If you're interested, this separation of browser and server elements is done by Sycamore, and you can learn more about it [here](https://docs.rs/sycamore/0.6/sycamore/generic_node/trait.GenericNode.html). +The first is the import of [`Html`](https://docs.rs/sycamore/0.7/sycamore/generic_node/trait.Html.html), which we define as a type parameter on the `get_error_pages` function. This makes sure that we can compile these views on the client or the server as long as they're targeting HTML (Sycamore can also target other templating formats for completely different systems, like MacOS desktop apps). In this function, we also define a different error page for a 404 error, which will occur when a user tries to go to a page that doesn't exist. The fallback page (which we initialize `ErrorPages` with) is the same as last time, and will be called for any errors other than a _404 Not Found_. @@ -78,16 +78,15 @@ First, we import a whole ton of stuff: - `perseus` - `RenderFnResultWithCause` -- see below for an explanation of this - `Template` -- as before - - `GenericNode` -- as before + - `Html` -- as before (this is from Sycamore, but is re-exported by Perseus for convenience) - `http::header::{HeaderMap, HeaderName}` -- some types for adding HTTP headers to our page - `serde` - `Serialize` -- a trait for `struct`s that can be turned into a string (like JSON) - `Deserialize` -- a trait for `struct`s that can be *de*serialized from a string (like JSON) -- `std::rc::Rc` -- same as before, you can read more about `Rc`s [here](https://doc.rust-lang.org/std/rc/struct.Rc.html) - `sycamore` - `component` -- a macro that turns a function into a Sycamore component - - `template` -- the `template!` macro, same as before - - `Template as SycamoreTemplate` -- the output of the `template!` macro, aliased as `SycamoreTemplate` so it doesn't conflict with `perseus::Template`, which is very different + - `view` -- the `view!` macro, same as before + - `View` -- the output of the `view!` macro - `SsrNode` -- Sycamore's representation of a node that will only be rendered on the server (this is re-exported from Perseus as well for convenience) Then we define a number of different functions and a `struct`, each of which gets a section now. @@ -100,9 +99,9 @@ Any template can take arguments in Perseus, which should always be given inside ### `index_page()` -This is the actual component that your page is. By annotating it with `#[component(IndexPage)]`, we tell Sycamore to turn it into a complex `struct` that can be called inside `template!` (which we do in `template_fn()`), and the `#[perseus::template(IndexPage)]` tells Perseus to do a little bit of work behind the scenes so that you can use `index_page` directly in the later `.template()` call. In previous versions of Perseus, you needed to do that boilerplate work yourself. +This is the actual component that your page is. By annotating it with `#[component(IndexPage)]`, we tell Sycamore to turn it into a complex `struct` that can be called inside `view!` (which we do in `template_fn()`), and the `#[perseus::template(IndexPage)]` tells Perseus to do a little bit of work behind the scenes so that you can use `index_page` directly in the later `.template()` call. In previous versions of Perseus, you needed to do that boilerplate work yourself. -Note that `index_page()` takes `IndexPageProps` as an argument, which it can then access in the `template!`. This is Sycamore's interpolation system, which you can read about [here](https://sycamore-rs.netlify.app/docs/basics/template), but all you need to know is that it's basically seamless and works exactly as you'd expect. +Note that `index_page()` takes `IndexPageProps` as an argument, which it can then access in the `view!`. This is Sycamore's interpolation system, which you can read about [here](https://sycamore-rs.netlify.app/docs/basics/template), but all you need to know is that it's basically seamless and works exactly as you'd expect. The only other thing we do here is define an `` (an HTML link) to `/about`. This link, and any others you define, will automatically be detected by Sycamore's systems, which will pass them to Perseus' routing logic, which means your users **never leave the page**. In this way, Perseus only pulls in the content that needs to change, and gives your users the feeling of a lightning-fast and weightless app. @@ -110,7 +109,7 @@ _Note: external links will automatically be excluded from this, and you can excl ### `head()` -This function is very similar to `index_page()`, except that it isn't a fully fledged Sycamore component, it just returns a `template! {}` instead. What this is used for is to define the content of the ``, which is metadata for your website, like its ``. As you can see, this is given the properties that `index_page()` takes, but we aren't using them for anything in this example. The `#[perseus::head]` macro tells Perseus to do some boilerplate work behind the scenes that's very similar to that done with `index_page`, but specialized for the `<head>`. +This function is very similar to `index_page()`, except that it isn't a fully fledged Sycamore component, it just returns a `view! {}` instead. What this is used for is to define the content of the `<head>`, which is metadata for your website, like its `<title>`. As you can see, this is given the properties that `index_page()` takes, but we aren't using them for anything in this example. The `#[perseus::head]` macro tells Perseus to do some boilerplate work behind the scenes that's very similar to that done with `index_page`, but specialized for the `<head>`. What's really important to note about this function is that it only renders to an `SsrNode`, which means you cannot use reactivity in here! Whatever is rendered the first time will be turned into a `String` and then statically interpolated into the document's `<head>`. @@ -140,7 +139,7 @@ This is just the equivalent of `.template()` for the `head()` function, and it d This function is part of Perseus' secret sauce (actually _open_ sauce), and it will be called when the CLI builds your app to create properties that the template will take (it expects a string, hence the serialization). Here, we just hard-code a greeting in to be used, but the real power of this comes when you start using the fact that this function is `async`. You might query a database to get a list of blog posts, or pull in a Markdown documentation page and parse it, the possibilities are endless! -This function returns a rather special type, `RenderFnResultWithCause<IndexPageProps>`, which declares that your function will return `IndexPageProps` if it succeeds, and a special error if it fails. That error can be anything you want (it's a `Box<dyn std::error::Error>` internally), but it will also have a blame assigned to it that records whether it was the server or the client that caused the error, which will impact the final HTTP status code. You can use the `blame_err!` macro to create these errors easily, but any time you use `?` in functions that return this type will simply use the default of blaming the server and returning an HTTP status code of *500 Internal Server Error*. +This function returns a rather special type, `RenderFnResultWithCause<IndexPageProps>`, which declares that your function will return `IndexPageProps` if it succeeds, and a special error if it fails. That error can be anything you want (it's a `Box<dyn std::error::Error>` internally), but it will also have a blame assigned to it that records whether it was the server or the client that caused the error, which will impact the final HTTP status code. You can use the `blame_err!` macro to create these errors easily, but any time you use `?` in functions that return this type will simply use the default of blaming the server and returning an HTTP status code of _500 Internal Server Error_. It may seem a little pointless to blame the client in the build process, but the reason this can happen is because, in more advanced uses of Perseus (particularly [incremental generation](:strategies/incremental)), this function could be called as a result of a client's request with parameters that it provides, which could be invalid. Essentially, know that it's a thing that's important in more complex use-cases of Perseus. diff --git a/docs/next/en-US/templates/intro.md b/docs/next/en-US/templates/intro.md index 1dab2c4c2b..e787991f27 100644 --- a/docs/next/en-US/templates/intro.md +++ b/docs/next/en-US/templates/intro.md @@ -60,4 +60,4 @@ It's often necessary to make sure you're only running some logic on the client-s This is a very contrived example, but what you should note if you try this is the flash from `server` to `client` (when you go to the page from the URL bar, not when you go in from the link on the index page), because the page is pre-rendered on the server and then hydrated on the client. This is an important principle of Perseus, and you should be aware of this potential flashing (easily solved by a less contrived example) when your users [initially load](:advanced/initial-loads) a page. -One important thing to note with this macro is that it will only work in a _reactive scope_ because it uses Sycamore's [context system](https://sycamore-rs.netlify.app/docs/advanced/contexts). In other words, you can only use it inside a `template!`, `create_effect`, or the like. +One important thing to note with this macro is that it will only work in a _reactive scope_ because it uses Sycamore's [context system](https://sycamore-rs.netlify.app/docs/advanced/contexts). In other words, you can only use it inside a `view!`, `create_effect`, or the like. diff --git a/docs/next/en-US/templates/metadata-modification.md b/docs/next/en-US/templates/metadata-modification.md index 20354ace8c..862be9a643 100644 --- a/docs/next/en-US/templates/metadata-modification.md +++ b/docs/next/en-US/templates/metadata-modification.md @@ -2,7 +2,7 @@ A big issue with only having one `index.html` file for your whole app is that you don't have the ability to define different `<title>`s and HTML metadata for each page. -However, Perseus overcomes this easily by allowing you to specify `.head()` on a `Template<G>`, which should be a function that returns a `Template<SsrNode>` (but you can use write `perseus::HeadFn` as the return type, it's an alias for that). The `template!` you define here will be rendered to a `String` and directly interpolated into the `<head>` of any pages this template renders. If you need it to be different based on the properties, you're covered, it takes the same properties as the normal template function! (They're deserialized automatically by the `#[perseus::head]` macro.) +However, Perseus overcomes this easily by allowing you to specify `.head()` on a `Template<G>`, which should be a function that returns a `Template<SsrNode>` (but you can use write `perseus::HeadFn` as the return type, it's an alias for that). The `view!` you define here will be rendered to a `String` and directly interpolated into the `<head>` of any pages this template renders. If you need it to be different based on the properties, you're covered, it takes the same properties as the normal template function! (They're deserialized automatically by the `#[perseus::head]` macro.) The only particular thing to note here is that, because this is rendered to a `String`, this **can't be reactive**. Variable interpolation is fine, but after it's been rendered once, the `<head>` **will not change**. If you need to update it later, you should do that with [`web_sys`](https://docs.rs/web-sys), which lets you directly access any DOM element with similar syntax to JavaScript (in fact, it's your one-stop shop for all things interfacing with the browser, as well as it's companion [`js-sys`](https://docs.rs/js-sys)). diff --git a/docs/next/en-US/what-is-perseus.md b/docs/next/en-US/what-is-perseus.md index f35be65280..fa3690acdb 100644 --- a/docs/next/en-US/what-is-perseus.md +++ b/docs/next/en-US/what-is-perseus.md @@ -36,7 +36,7 @@ use sycamore::prelude::*; let counter = Signal::new(0); let increment = cloned!((counter) => move |_| counter.set(*counter.get() + 1)); -template! { +view! { p { (counter.get()) } button(on:click = increment) { "Increment" } } @@ -62,12 +62,12 @@ To our knowledge, the only other framework in the world right now that supports [Benchmarks show](https://rawgit.com/krausest/js-framework-benchmark/master/webdriver-ts-results/table.html) that [Sycamore](https://sycamore-rs.netlify.app) is slightly faster than [Svelte](https://svelte.dev) in places, one of the fastest JS frameworks ever. Perseus uses it and [Actix Web](https://actix.rs) or [Warp](https://github.com/seanmonstar/warp) (either is supported), some of the fastest web servers in the world. Essentially, Perseus is built on the fastest tech and is itself made to be fast. -The speed of web frameworks is often measured by [Lighthouse](https://developers.google.com/web/tools/lighthouse) scores, which are scores out of 100 (higher is better) that measure a whole host of things, like *total blocking time*, *first contentful paint*, and *time to interactive*. These are then aggregated into a final score and grouped into three brackets: 0-49 (slow), 50-89 (medium), and 90-100 (fast). This website, which is built with Perseus, using [static exporting](:exporting) and [size optimizations](:deploying/size), consistently scores a 100 on desktop and above 90 for mobile. You can see this for yourself [here](https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Farctic-hen7.github.io%2Fperseus%2Fen-US%2F&tab=desktop) on Google's PageSpeed Insights tool. +The speed of web frameworks is often measured by [Lighthouse](https://developers.google.com/web/tools/lighthouse) scores, which are scores out of 100 (higher is better) that measure a whole host of things, like _total blocking time_, _first contentful paint_, and _time to interactive_. These are then aggregated into a final score and grouped into three brackets: 0-49 (slow), 50-89 (medium), and 90-100 (fast). This website, which is built with Perseus, using [static exporting](:exporting) and [size optimizations](:deploying/size), consistently scores a 100 on desktop and above 90 for mobile. You can see this for yourself [here](https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Farctic-hen7.github.io%2Fperseus%2Fen-US%2F&tab=desktop) on Google's PageSpeed Insights tool. <details> <summary>Why not 100 on mobile?</summary> -The only issue that prevents Perseus from achieving a consistent perfect score on mobile is *total blocking time*, which measures the time between when the first content appears on the page and wehn that content is interactive. Of course, WebAssembly code is used for this part (compiled from Rust), which isn't completely optimized for yet on many mobile devices. As mobile browsers get better at parsing WebAssembly, TBT will likely decrease further from the medium range to the green range (which we see for more poerful desktop systems). +The only issue that prevents Perseus from achieving a consistent perfect score on mobile is _total blocking time_, which measures the time between when the first content appears on the page and wehn that content is interactive. Of course, WebAssembly code is used for this part (compiled from Rust), which isn't completely optimized for yet on many mobile devices. As mobile browsers get better at parsing WebAssembly, TBT will likely decrease further from the medium range to the green range (which we see for more poerful desktop systems). </details> @@ -86,7 +86,7 @@ Basically, here's your workflow: ## How stable is it? -Perseus is considered reasonably stable at this point, though it still can't be recommended for *production* usage just yet. That said, this very website is built entirely with Perseus and Sycamore, and it works pretty well! +Perseus is considered reasonably stable at this point, though it still can't be recommended for _production_ usage just yet. That said, this very website is built entirely with Perseus and Sycamore, and it works pretty well! For now though, Perseus is perfect for anything that doesn't face the wider internet, like internal tools, personal projects, or the like. Just don't use it to run a nuclear power plant, okay?