Skip to content

Commit

Permalink
feat: upgrade to Sycamore v0.8.0 (#142)
Browse files Browse the repository at this point in the history
* chore: updated versions and editions

Sycamore v0.8 only runs on Rust 2021.

* fix: fixed some rust 2021 syntax issues

* fix: fixed easy errors in `perseus` crate

Still have to do:
- `router/`
- `template/`
- `shell.rs`
- One tricky error in `error_pages.rs`

* fix: fixed all errors in `perseus` crate

I removed the troublesome function in `error_pages.rs`, so that may bite
me soon.

* fix: fixed clippy lints

* fix: fixed macros and made `basic` example work

* fix: partial fixes for `rx_state`

Still have problems with `bind:value` (as we will in all the state
platform examples for now).

* fix: updated state examples (errors persist)

The remaining errors will be fixed after `make_rx` can work with
non-`Rc` `Signal`s.

* fix: updated all examples (lifetime errors persist)

* fix: fixed engine

Nothing works without hydration disabled though...

* refactor: made renderers use top-level router context

This *should* make the state platform work with lifetimes.

* feat: rewrote render context to use `Signal`s

* fix: made macros work with the new render context logic

* fix: updated unreactive macros and example

* chore: tmp commit before rollback

* revert: revert to before axing `RcSignal`s

I have been shown a much better way of achieving the same outcome.

* revert: return to previous changes

It will be easier to manually undo changes to make sure we preserve some
good things.

This reverts commit 15187b5.

* feat: moved back to `RcSignal`s

This avoids a huge number of lifetime issues, and actually ends up being
more performant, without compromising on ergonomics.

* feat: made `struct` given to user's template use `&'a RcSignal<T>`

This should make Perseus several orders of magnitude more ergonomic, in
line with Sycamore's new no-clones system!

* fix: fixed global state functionality in the macros

This requires an irritating change to import practices unfortunately,
but the convenience and ergonomics are worth it.

* fix: fixed lifetimes errors in all examples

* fix: fixed all lifetimes issues

This also involved some minor changes to the macros to fix some nested
state issues.

* fix: fixed nested state references

This improves ergonomics and makes the auth example compile.

* fix: fixed hydration by not inserting hydration keys in `<head>` (#137)

* refactor: simplify provide_context_signal_replace

Also slightly improves performance in only making a single call to use_context

* fix: do not insert hydration keys in the head string

* chore: remove perseus/hydrate feature from Cargo.toml

* chore: merge imports for consistent code style

* fix: update sycamore to v0.8.0-beta.5 and remove workaround

Co-authored-by: arctic_hen7 <arctic_hen7@pm.me>

* chore: updated deps after #137

These were just for the demos that weren't ready at the time of the PR.

* chore: re-added `hydrate` feature to `basic` example

Hydration still doesn't work in the `auth` example.

* chore: removed unused dep

* fix: fixed doc tests issue

* fix: fixed naming of `PerseusRoot` (was wrongly `perseus_root`)

* feat: updated `index_view` example

* chore: updated to latest sycamore beta

This should fix the issues with the `body` element.

* fix: fixed an imports issue with latest sycamore beta

* fix: fixed types to make i18n work

* test: fixed `rx_state` tests for slightly updated structure

* fix: ignored a failing doctest

* docs: updated security.md for next beta version

* docs: added new docs for v0.4.x

Also locked the old v0.3.4-5 docs to a specific commit hash, which keeps
the examples there safe to use.

Co-authored-by: Luke Chu <37006668+lukechu10@users.noreply.github.com>
  • Loading branch information
arctic-hen7 and lukechu10 authored May 30, 2022
1 parent eccf137 commit b14b4e0
Show file tree
Hide file tree
Showing 188 changed files with 3,484 additions and 1,070 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,20 @@ Perseus is a blazingly fast frontend web development framework built in Rust wit

Here's a taste of Perseus (see [the _tiny_ example](https://github.com/arctic-hen7/perseus/tree/main/examples/comprehensive/tiny) for more):

```rust
```rust,ignore
use perseus::{Html, PerseusApp, Template};
use sycamore::view;
#[perseus::main]
pub fn main<G: Html>() -> PerseusApp<G> {
PerseusApp::new().template(|| {
Template::new("index").template(|_| {
view! {
Template::new("index").template(|cx, _| {
view! { cx,
p { "Hello World!" }
}
})
})
}

```

Check out [the book](https://arctic-hen7.github.io/perseus/en-US/docs) to learn how to turn that into your next app!
Expand Down
11 changes: 6 additions & 5 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

Currently, the following versions are receiving security updates.

| Version | Supported |
| ------- | ------------------ |
| 0.3.x | :white_check_mark: |
| 0.2.x | ❌ (since December 14th 2021) |
| 0.1.x | ❌ (since September 30th 2021) |
| Version | Supported |
|---------|--------------------------------|
| 0.4.x | :white_check_mark: (beta) |
| 0.3.x | :white_check_mark: |
| 0.2.x | ❌ (since December 14th 2021) |
| 0.1.x | ❌ (since September 30th 2021) |

Please note that v0.3.x is still currently in beta, though due to significant infrastructural changes, security updates are not being issued for v0.2.x.

Expand Down
87 changes: 87 additions & 0 deletions docs/0.4.x/en-US/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Introduction

- [Introduction](/docs/intro)
- [What is Perseus?](/docs/what-is-perseus)
- [Core Principles](/docs/core-principles)
- [Hello World!](/docs/tutorials/hello-world)
- [Your Second App](/docs/tutorials/second-app)

---

# Reference

- [`PerseusApp`](/docs/reference/perseus-app)
- [`define_app!`](/docs/reference/define-app)
- [Writing Views](/docs/reference/views)
- [The Index View](/docs/reference/index-view)
- [Debugging](/docs/reference/debugging)
- [Live Reloading](/docs/reference/live-reloading)
- [Templates and Routing](/docs/reference/templates/intro)
- [Modifying the `<head>`](/docs/reference/templates/metadata-modification)
- [Modifying HTTP Headers](/docs/reference/templates/setting-headers)
- [Listening to the Router](/docs/reference/templates/router-state)
- [Error Pages](/docs/reference/error-pages)
- [Static Content](/docs/reference/static-content)
- [Internationalization](/docs/reference/i18n/intro)
- [Defining Translations](/docs/reference/i18n/defining)
- [Using Translations](/docs/reference/i18n/using)
- [Translations Managers](/docs/reference/i18n/translations-managers)
- [Other Translation Engines](/docs/reference/i18n/other-engines)
- [Rendering Strategies](/docs/reference/strategies/intro)
- [Build State](/docs/reference/strategies/build-state)
- [Build Paths](/docs/reference/strategies/build-paths)
- [Request State](/docs/reference/strategies/request-state)
- [Revalidation](/docs/reference/strategies/revalidation)
- [Incremental Generation](/docs/reference/strategies/incremental)
- [State Amalgamation](/docs/reference/strategies/amalgamation)
- [Hydration](/docs/reference/hydration)
- [Reactive State](/docs/reference/state/rx)
- [Global State](/docs/reference/state/global)
- [State Freezing](/docs/reference/state/freezing)
- [Freezing to IndexedDB](/docs/reference/state/idb-freezing)
- [Hot State Reloading (HSR)](/docs/reference/state/hsr)
- [CLI](/docs/reference/cli)
- [Ejecting](/docs/reference/ejecting)
- [Snooping](/docs/reference/snooping)
- [Testing](/docs/reference/testing/intro)
- [Checkpoints](/docs/reference/testing/checkpoints)
- [Fantoccini Basics](/docs/reference/testing/fantoccini-basics)
- [Manual Testing](/docs/reference/testing/manual)
- [Styling](/docs/reference/styling)
- [Communicating with a Server](/docs/reference/server-communication)
- [Stores](/docs/reference/stores)
- [Static Exporting](/docs/reference/exporting)
- [Plugins](/docs/reference/plugins/intro)
- [Functional Actions](/docs/reference/plugins/functional)
- [Control Actions](/docs/reference/plugins/control)
- [Using Plugins](/docs/reference/plugins/using)
- [The `tinker` Action](/docs/reference/plugins/tinker)
- [Writing Plugins](/docs/reference/plugins/writing)
- [Security Considerations](/docs/reference/plugins/security)
- [Publishing Plugins](/docs/reference/plugins/publishing)
- [Engines](/docs/reference/engines)
- [Deploying](/docs/reference/deploying/intro)
- [Server Deployment](/docs/reference/deploying/serverful)
- [Serverless Deployment](/docs/reference/deploying/serverless)
- [Optimizing Code Size](/docs/reference/deploying/size)
- [Relative Paths](/docs/reference/deploying/relative-paths)
- [Docker Deployment](/docs/reference/deploying/docker)
- [Migrating from v0.3.x](/docs/reference/updating)
- [Common Pitfalls and Known Bugs](/docs/reference/pitfalls-and-bugs)

---

# Advanced

- [Under the Hood](/docs/advanced/intro)
- [Architecture](/docs/advanced/arch)
- [Initial Loads](/docs/advanced/initial-loads)
- [Subsequent Loads](/docs/advanced/subsequent-loads)
- [Routing](/docs/advanced/routing)
- [Route Announcer](/docs/advanced/route-announcer)

---

# Further Tutorials

- [Authentication](docs/tutorials/auth)
49 changes: 49 additions & 0 deletions docs/0.4.x/en-US/advanced/arch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Architecture

Perseus has several main components:

- `perseus` -- the core module that defines everything necessary to build a Perseus app if you try hard enough
- `perseus-actix-web` -- an integration that makes it easy to run Perseus on the [Actix Web](https://actix.rs) framework
- `perseus-warp` -- an integration that makes it easy to run Perseus on the [Warp](https://github.com/seanmonstar/warp) framework
- `perseus-cli` -- the command-line interface used to run Perseus apps conveniently
- `perseus-engine` -- an internal crate created by the CLI responsible for building an app
- `perseus-engine-server` -- an internal crate created by the CLI responsible for serving an app and performing runtime logic

## Core

At the core of Perseus is the [`perseus`](https://docs.rs/perseus) module, which is used for nearly everything in Perseus. In theory, you could build a fully-functional app based on this crate alone, but you'd be reinventing the wheel at least three times. This crate exposes types for the i18n systems, configuration management, routing, and asset fetching, most of which aren't intended to be used directly by the user.

What is intended to be used directly is the `Template<G>` `struct`, which is integral to Perseus. This stores closures for every rendering strategy, which are executed as provided and necessary at build and runtime. Note that these are all stored in `Rc`s, and `Template<G>`s are cloned.

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.

## 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, 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)).

### CLI Builder

This system can be further broken down into two parts.

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

#### App Shell

This is encapsulated in `.perseus/src/lib.rs`, and it performs a number of integral functions:

- Ensures that any `panic!`s or the like ar printed properly in the browser console
- Creates and manages the internal router
- Renders your actual app
- Handles locale detection
- Invokes the core app shell to manage initial/subsequent loads and translations
- Handles error page displaying

### CLI Server

This is just an invocation of the the appropriate server integration's systems with the data provided by the user through `PerseusApp`.
23 changes: 23 additions & 0 deletions docs/0.4.x/en-US/advanced/initial-loads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Initial Loads

Perseus handles _initial loads_ very differently from _subsequent loads_. The former refers to what's done when a user visits a page on a Perseus app from an external source (e.g. visiting from a search engine, redirected from another site), and this requires a full HTMl page to be sent that can be interpreted by the browser. By contrast, subsequent loads are loads between pages within the same Perseus app, which can be performed by the app shell (described in the next section).

The process of initial loads is slightly complex, and occurs like so (this example is for a page called `/posts/test`, rendered with incremental generation):

1. Browser requests `/posts/test` from the server.
2. Server matches requested URL to wildcard (`*`) and handles it with the server-side inferred router, determining which `Template<G>` to use.
3. Server calls internal core methods to render the page (using incremental generation strategy, but it doesn't need to know that), producing an HTML snippet and a set of JSON properties.
4. Server calls `template.render_head_str()` and injects the result into the document's `<head>` (avoiding `<title>` flashes and improving SEO) after a delimiter comment that separates it from the metadata on every page (which is hardcoded into `index.html`).
5. Server interpolates JSON state into `index.html` as a global variable in a `<script>`.
6. Server interpolates HTML snippet directly into the user's `index.html` file.
7. Server sends final HTML package to client, including Wasm (injected at build-time).
8. Browser renders HTML package, user sees content immediately.
9. Browser invokes Wasm, hands control to the app shell.
10. App shell checks if initial state declaration global variable is present, finds that it is and unsets it (so that it doesn't interfere with subsequent loads).
11. App shell moves server-rendered content out of `__perseus_content_initial` and into `__perseus_content_rx`, which Sycamore's router had control over (allowing it to catch links and use the subsequent loads system).
12. App shell gets a translator if the app uses i18n.
13. App shell hydrates content at `__perseus_content_rx` with Sycamore and returns, the page is now interactive and has a translator context.

Note: if this app had used i18n, the server would've returned the app shell with no content, and the app shell, when invoked, would've immediately redirected the user to their preferred locale (or the closest equivalent).

The two files integral to this process are [`initial_load.rs`](https://github.com/arctic-hen7/perseus/blob/main/packages/perseus-actix-web/src/initial_load.rs) and [`shell.rs`](https://github.com/arctic-hen7/perseus/blob/main/packages/perseus/src/shell.rs).
3 changes: 3 additions & 0 deletions docs/0.4.x/en-US/advanced/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Under the Hood

This section of the documentation is devoted to explaining the inner workings of Perseus, which will be particularly useful if you choose to eject from the CLI's harness or if you want to contribute to Perseus!
13 changes: 13 additions & 0 deletions docs/0.4.x/en-US/advanced/route-announcer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Route Announcer

Perseus uses a routing system separate to the browser's, as is typical of SPA and hybrid frameworks. However, while this means we download fewer resources for each page transition, this does mean that screen readers often won't know when a page change occurs. Of course, this is catastrophic for accessibility for vision-impaired users, so Perseus follows the example set by other frameworks and uses a *route announcer*. This is essentially just a glorified `<p>` element with the ID `__perseus_route_announcer` (so that you can make modifications to it imperatively if necessary) that's updated to tell the user the title of the current page.

When a user enters a session in your app (i.e. when they open your app from a link that's not inside your app), the browser can announce the page to any screen readers as usual, so the route announcer will start empty. However, on every subsequent page load in your app (all of which will use Perseus' custom router), the route announcer will be updated to declare the title of the page as it can figure it out. It does so in this order:

1. The `<title>` element of the page.
2. The first `<h1>` element on the page.
3. The page's URL (e.g. `/en-US/about`).

This prioritization is the same as used by NextJS, and Perseus' route announcer is heavily based on NextJS'.

Notably, the route announcer is invisible to the naked eye, and it will only 'appear' through a screen reader. This is achieved through some special styling optimized for displaying this kind of text, again inspired by NextJS' router announcer (which has been proven to work very well in long-term production).
38 changes: 38 additions & 0 deletions docs/0.4.x/en-US/advanced/routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Routing

Perseus' routing system is quite unique in that it's almost entirely _inferred_, meaning that you don't ever have to define a router or explain to the system which paths go where. Instead, they're inferred from templates in a system that's explained in detail in the [templates section](:templates/intro).

## Template Selection Algorithm

Perseus has a very specific algorithm that it uses to determine which template to use for a given route, which is greatly dependent on `.perseus/dist/render_conf.json`. This is executed on the client-side for _subsequent loads_ and on the server-side for _initial loads_.

Here's an example render configuration (for the [showcase example](https://github.com/arctic-hen7/perseus/blob/main/examples/showcase)), which maps path to template root path.

```json
{
"about": "about",
"index": "index",
"post/new": "post/new",
"ip": "ip",
"post/*": "post",
"timeisr/test": "timeisr",
"timeisr/*": "timeisr",
"time": "time",
"amalgamation": "amalgamation",
"post/blah/test/blah": "post",
"post/test": "post"
}
```

Here are the algorithm's steps (see [`router.rs`](https://github.com/arctic-hen7/perseus/blob/main/packages/perseus/src/router.rs)):

1. If the path is empty, set it to `index` (which is used for the landing page).
2. Try to directly get the template name by trying the path as a key. This would work for anything not using incremental generation (in the above example, anything other than `post/*`).
3. Split the path into sections by `/` and iterate through them, performing the following on each section (iterating forwards from the beginning of the path, becoming more and more specific):
1. Make a path out of all segments up to the current point, adding `/*` at the end (indicative of incremental generation in the render configuration).
2. Try that as a key, return if it works.
3. Even if we have something, continue iterating until we have nothing. This way, we get the most specific path possible (and we can have incremental generation in incremental generation).

## Relationship with Sycamore's Router

Sycamore has its own [routing system](https://sycamore-rs.netlify.app/docs/v0.6/advanced/routing), which Perseus depends on extensively under the hood. This is evident in `.perseus/src/lib.rs`, which invokes the router. However, rather than using the traditional Sycamore approach of having an `enum` with variants for each possible route (which was the approach in Perseus v0.1.x), Perseus provides the router with a `struct` that performs routing logic and returns either `RouteVerdict::Found`, `RouteVerdict::LocaleDetection`, or `RouteVerdict::NotFound`. The render configuration is accessed through a global variable implanted in the user's HTML shell when the server initializes.
20 changes: 20 additions & 0 deletions docs/0.4.x/en-US/advanced/subsequent-loads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Subsequent Loads

if the user follows a link inside a Perseus app to another page within that same app, the Sycamore router will catch it and prevent the browser from requesting the new file from the server. The following will then occur (for an `/about` page rendered simply):

1. Sycamore router calls Perseus inferred router logic.
2. Perseus inferred router determines from new URL that template `about` should be used, returns to Sycamore router.
3. Sycamore router passes that to closure in `perseus-cli-builder` shell, which executes core app shell.
4. App shell checks if an initial load declaration global variable is present and finds none, hence it will proceed with the subsequent load system.
5. App shell fetches page data from `/.perseus/page/<locale>/about?template_name=about` (if the app isn't using i18n, `<locale>` will verbatim be `xx-XX`).
6. Server checks to ensure that locale is supported.
7. Server renders page using internal systems (in this case that will just return the static HTML file from `.perseus/dist/static/`).
8. Server renders document `<head>`.
9. Server returns JSON of HTML snippet (not complete file), stringified properties, and head.
10. App shell deserializes page data into state and HTML snippet.
11. App shell interpolates HTML snippet directly into `__perseus_content_rx` (which Sycamore router controls), user can now see new page.
12. App shell interpolates new document `<head>`.
13. App shell initializes translator if the app is using i18n.
14. App shell hydrates content at `__perseus_content_rx`, page is now interactive.

The two files integral to this process are [`page_data.rs`](https://github.com/arctic-hen7/perseus/blob/main/packages/perseus-actix-web/src/page_data.rs) and [`shell.rs`](https://github.com/arctic-hen7/perseus/blob/main/packages/perseus/src/shell.rs).
Loading

0 comments on commit b14b4e0

Please sign in to comment.