diff --git a/docs/0.3.x/en-US/SUMMARY.md b/docs/0.3.x/en-US/SUMMARY.md index 264d957a64..51526c8bf3 100644 --- a/docs/0.3.x/en-US/SUMMARY.md +++ b/docs/0.3.x/en-US/SUMMARY.md @@ -47,6 +47,7 @@ - [The `tinker` Action](/docs/plugins/tinker) - [Writing Plugins](/docs/plugins/writing) - [Security Considerations](/docs/plugins/security) + - [Publishing Plugins](/docs/plugins/publishing) - [Deploying](/docs/deploying/intro) - [Server Deployment](/docs/deploying/serverful) - [Serverless Deployment](/docs/deploying/serverless) diff --git a/docs/0.3.x/en-US/advanced/arch.md b/docs/0.3.x/en-US/advanced/arch.md index c77662fe01..1fb65ed074 100644 --- a/docs/0.3.x/en-US/advanced/arch.md +++ b/docs/0.3.x/en-US/advanced/arch.md @@ -17,25 +17,23 @@ What is intended to be used directly is the `Template` `struct`, which is int The other commonly used system from this crate is the `Translator` system, explained in detail in [the i18n section](:i18n/intro). `Translator`s are passed around in `Rc`s, and `TranslationsManager` on the server caches all translations by default in memory on the server. -## Actix Web Integration +## Server Integrations -The core of Perseus provides very few systems to set up a functional Perseus server though, which requires a significant amount of additional work. To this end, [`perseus-actix-web`](https://docs.rs/perseus-actix-web) is used to make this process easy. If you've ejected, you'll be working with this directly, which should be relatively simple, as it just accepts configuration options and then should simply work. - -Note that this module provides a `configurer` function, which allows it to be modularly added to any existing Actix Web server, which is particularly useful if you want to run other endpoint on your server, or a system like [Diana](https://github.com/arctic-hen7/diana). +The core of Perseus provides very few systems to set up a functional Perseus server though, which requires a significant amount of additional work. To this end, server integration crates are used to make this process easy. If you've ejected, you'll be working with these directly, which should be relatively simple, as they just accept configuration options and then should simply work. ## CLI -As documented in [this section](:cli), the CLI simply runs commands to execute the last two components of the Perseus system, acting as a convenience. It also contains these two components inside its binary (using [`include_dir!`](https://github.com/Michael-F-Bryan/include_dir)) +As documented in [this section](:cli), the CLI simply runs commands to execute the last two components of the Perseus system, acting as a convenience. It also contains these two components inside its binary (using [`include_dir!`](https://github.com/Michael-F-Bryan/include_dir)). -## CLI Builder +### CLI Builder This system can be further broken down into two parts. -### Static Generator +#### Static Generator -This is a single binary that just imports the user's templates and some other information (like locales) and then calls `build_app`. This will result in generating a number of files to `.perseus/dist`, which will be served by the server to any clients, which will then hydrate those static pages into fully-fledged Sycamore templates. +This is a single binary that just imports the user's templates and some other information (like locales) and then calls `build_app`. This will result in generating a number of files to `.perseus/dist/`, which will be served by the server to any clients, which will then hydrate those static pages into fully-fledged Sycamore templates. -### App Shell +#### App Shell This is encapsulated in `.perseus/src/lib.rs`, and it performs a number of integral functions: @@ -46,6 +44,6 @@ This is encapsulated in `.perseus/src/lib.rs`, and it performs a number of integ - Invokes the core app shell to manage initial/subsequent loads and translations - Handles error page displaying -## CLI Server +### CLI Server -This is just an invocation of the `perseus-actix-web` module's systems with the data provided by the user through the `define_app!` macro. This also sets the default location for static content and the `index.html` file. +This is just an invocation of the the appropriate server integration's systems with the data provided by the user through the `define_app!` macro. This also sets the default location for static content and the `index.html` file. diff --git a/docs/0.3.x/en-US/hello-world.md b/docs/0.3.x/en-US/hello-world.md index c6a1c53bdf..58c3e76505 100644 --- a/docs/0.3.x/en-US/hello-world.md +++ b/docs/0.3.x/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/0.3.x/en-US/pitfalls-and-bugs.md b/docs/0.3.x/en-US/pitfalls-and-bugs.md index e2981f8caa..e1e15ff7d6 100644 --- a/docs/0.3.x/en-US/pitfalls-and-bugs.md +++ b/docs/0.3.x/en-US/pitfalls-and-bugs.md @@ -20,3 +20,7 @@ wasm-opt = false ``` This will disable optimizations for your Wasm bundle, which prevents this issue from occurring. Note however that you'll end up with very large bundles if you compile on your M1 Mac. Again though, this issue is set to be fixed very soon. + +## I want to apply X to my `Cargo.toml`, but it doesn't work + +Perseus has a rather unique code structure that will foil most attempts at modifying your own `Cargo.toml`. For example, if you wanted to change the `codegen_units` in the release profile of your app, you couldn't do this in your own `Cargo.toml`, it would have no effect. The reason for this is that the code your write is actually a library that's imported by the CLI's engines, so any custom configuration has to be made directly on the engines. In other words, you'll need to apply your changes on `.perseus/Cargo.toml` instead. You can also apply customizations on the server and the builder, which are separate crates under `.perseus/`. Note that modifying `.perseus/` and retaining your changes requires [ejecting](:ejecting), or you could [write a plugin](:plugins/writing) if it's a change you make a lot. diff --git a/docs/0.3.x/en-US/plugins/publishing.md b/docs/0.3.x/en-US/plugins/publishing.md new file mode 100644 index 0000000000..54eb11ea1b --- /dev/null +++ b/docs/0.3.x/en-US/plugins/publishing.md @@ -0,0 +1,13 @@ +# Publishing Plugins + +After you've written a plugin, you can either use it locally, or you can publish it to the world on , Rust's package registry. That will mean anyone in the world can use it in their own code, and you'll be contributing to the Perseus community! It's usual to name plugins beginning with `perseus-` (e.g. `perseus-size-opt`), but this isn't required. + +Perseus also maintains a registry of all plugins that have been published, but we rely on users to let us know about their plugins. You can do this by [opening an issue](https://github.com/arctic-hen7/perseus/issues/new/choose) on the Perseus repository, and we'll be happy to include your project! + +## Trusted Plugins + +You may have noticed that some plugins in the Perseus registry have ticks next to them. These plugins are _trusted_, meaning they've been reviewed by the Perseus team and are considered to be high quality and safe to use. Note however that this is in no way a guarantee of quality, and that a trusted plugin may still contain malware or bugs, and that the Perseus team is in no way responsible for any plugin on the registry. + +If you'd like to apply for your plugin to be trusted after it's been listed on the registry, reach out to the Perseus maintainer [by email](mailto:arctic_hen7@pm.me), and a code review will be happily undertaken. + +By the same token though, an untrusted plugin is not in any way an indication that a plugin is low quality or malicious, it just means it hasn't been reviewed by the Perseus team. If you don't want to have your plugin reviewed, no problem! diff --git a/docs/0.3.x/en-US/second-app.md b/docs/0.3.x/en-US/second-app.md index c9c9f63410..1f95e34ca8 100644 --- a/docs/0.3.x/en-US/second-app.md +++ b/docs/0.3.x/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/0.3.x/en-US/templates/intro.md b/docs/0.3.x/en-US/templates/intro.md index 1dab2c4c2b..e787991f27 100644 --- a/docs/0.3.x/en-US/templates/intro.md +++ b/docs/0.3.x/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/0.3.x/en-US/templates/metadata-modification.md b/docs/0.3.x/en-US/templates/metadata-modification.md index 20354ace8c..862be9a643 100644 --- a/docs/0.3.x/en-US/templates/metadata-modification.md +++ b/docs/0.3.x/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/0.3.x/en-US/what-is-perseus.md b/docs/0.3.x/en-US/what-is-perseus.md index f35be65280..fa3690acdb 100644 --- a/docs/0.3.x/en-US/what-is-perseus.md +++ b/docs/0.3.x/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?