Skip to content

Commit

Permalink
docs: fixed typos in core-principles.md (#316)
Browse files Browse the repository at this point in the history
* Fix spelling errors in core-principles.md

Multiple grammatical errors, typos, and sentence structures fixed.

* Update docs/0.4.x/en-US/core-principles.md

Co-authored-by: Sam Brew <arctic.hen@pm.me>

* Update docs/0.4.x/en-US/core-principles.md

Co-authored-by: Sam Brew <arctic.hen@pm.me>

* Update docs/0.4.x/en-US/core-principles.md

Co-authored-by: Sam Brew <arctic.hen@pm.me>

---------

Co-authored-by: Sam Brew <arctic.hen@pm.me>
  • Loading branch information
OtsoBear and arctic-hen7 committed Jan 18, 2024
1 parent 0b98318 commit eeccf81
Showing 1 changed file with 11 additions and 11 deletions.
22 changes: 11 additions & 11 deletions docs/0.4.x/en-US/core-principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ There are two things you need to know about templates:
1. An app is split into templates, and each template is split into pages.
2. A page is generated from a template and state. **Template + state = page**

Anyone who's ever used a website before will be at least passingly familiar with the idea of *pages* --- they're things that display different content, each at a different route. For example, you might have a landing page at `https://example.com` and an about page at `https://example.com/about`.
Anyone who's ever used a website before will be at least passingly familiar with the idea of *pages* —— they're things that display different content, each at a different route. For example, you might have a landing page at `https://example.com` and an about page at `https://example.com/about`.

In Perseus, pages are never coded directly, they're generated by the engine from templates. Templates can be thought of as mathematical functions if you like: (crudely) a template `T` can be defined such that `T(x) = P`, where `x` is some state, and `P` is a page.
In Perseus, pages are never coded directly, they're generated by the engine from templates. Templates can be thought of as mathematical functions if you like: (crudely) a template `T` can be defined such that `T(x) = P`, where `x` is some state, and `P` is a page.

Let's take an example to understand how this works in practice. Let's say you're building a music player app that has a vast library of songs (we'll ignore playlists, artists, etc. to keep things simple). The first step in designing your app is thinking about its structure. It comes fairly quickly that you'll need an index page to show the top songs, an about page to tell people about the platform, and one page for each song. Now, the index and about pages have different structures, but every song page has the same structure, just with different content. This is where templates come in. You would have one template for the index page and another for the about page, and then you'd have a third template for the songs pages.
Let's take an example to understand how this works in practice. Let's say you're building a music player app that has a vast library of songs (we'll ignore playlists, artists, etc. to keep things simple). The first step in designing your app is to think about its structure. It comes fairly quickly that you'll need an index page to show the top songs, an about page to tell people about the platform, and one page for each song. Now, the index and about pages have different structures, but every song page has the same structure, just with different content. This is where templates come in. You would have one template for the index page and another for the about page, and then you'd have a third template for the songs pages.

That third template can take in some state, and produce a different page for every single song, but all with the same structure. You can see this kind of concept in action on this very website. Every page in the docs has the same heading up the top, footer down the bottom, and sidebar on the left (or in a menu if you're on mobile), but they all have different content. There's just one template involved for all this, which generates hundreds of pages (here, that same template generates pages for every version of Perseus ever).

Expand All @@ -29,21 +29,21 @@ Let's return to our music player app. Are all those songs listed in a database a

Once that state is generated, Perseus will go right ahead and proactively prerender your pages to HTML, meaning your users see content the second they load your site. (This is called server-side rendering, except the actual rendering has happened ahead of time, whenever you built your app.)

These ideas are built into Perseus at the core, and generating state for templates to generate pages is the fundamental idea behind Perseus. You'll find similar concepts in popular JavaScript frameworks like NextJS and GatsbyJS. It's Perseus' speed, ergonomics, and some things we'll explain in a moment that set it apart.
These ideas are built into Perseus at its core, and generating state for templates to generate pages is the fundamental idea behind Perseus. You'll find similar concepts in popular JavaScript frameworks like NextJS and GatsbyJS. It's Perseus' speed, ergonomics, and some things we'll explain in a moment that set it apart.

Once you've generated some state and you've got all the pages ready, there's still a log of work to be done on this music player app. A given song might be paused or playing, the user might have manually turned off dark mode, autoplaying related songs might be on or off. This is all state, but it's not state that we can handle when we build your app. Traditionally, frameworks would leave you on your own here to work this all out, but Perseus tries to be a little more helpful by *automatically making your state reactive*. Let's say the state for a single song page includes the properties `name`, `artist`, `album`, `year`, and `paused` (there'd probably be a lot more in reality though!). The first four can be set at build time and forgotten about, but `paused` could be changed at any time. No problem, you can change it once the page is loaded. Just call `.set()` on it and Perseus will not only update it locally, but it will update it in a store global to your app so that, if a user goes back to that song later, it will be preserved (or not, your choice). And what about things like `dark_mode`, state that's relevant to the whole app? Well, Perseus provides inbuilt support for reactive global state that can be interacted with from any page.
Once you've generated some state and you've got all the pages ready, there's still a lot of work to be done on this music player app. A given song might be paused or playing, the user might have manually turned off dark mode, autoplaying related songs might be on or off. This is all state, but it's not state that we can handle when we build your app. Traditionally, frameworks would leave you on your own here to work this all out, but Perseus tries to be a little more helpful by *automatically making your state reactive*. Let's say the state for a single song page includes the properties `name`, `artist`, `album`, `year`, and `paused` (there'd probably be a lot more in reality though!). The first four can be set at build time and forgotten about, but `paused` could be changed at any time. No problem, you can change it once the page is loaded. Just call `.set()` on it and Perseus will not only update it locally, but it will update it in a store global to your app so that, if a user goes back to that song later, it will be preserved (or not, your choice). And what about things like `dark_mode`, state that's relevant to the whole app? Well, Perseus provides inbuilt support for reactive global state that can be interacted with from any page.

Now, if you're familiar with user interface (UI) development, this might all sound familiar to you, it's very similar to the *MVC*, or *model, view, controller* pattern. If you've never heard of this, it's just a way of developing apps in which you hold all the states that your app can possibly be in in a *model* and use that to build a *view*. Then you handle user interaction with a *controller*, which modifies the state, and the *view* updates accordingly. Perseus doesn't force this structure on you, and in fact you can opt out entirely from all this reactive state business if it's not your cup of tea with no problems, because Perseus doesn't use MVC as a *pattern* that you develop in, it uses it as an *architecture* that your code works in. You can use development patterns from 1960 or 2060 if you want, Perseus doesn't mind, it'll just work away in the background and make sure your app's state *just works*.
Now, if you're familiar with user interface (UI) development, this might all sound familiar to you, it's very similar to the *MVC*, or *model, view, controller* pattern. If you've never heard of this, it's just a way of developing apps in which you hold all the states that your app can possibly be in in a *model* and use that to build a *view*. Then you handle user interaction with a *controller*, which modifies the state, and the *view* updates accordingly. Perseus doesn't force this structure on you, and in fact, you can opt-out entirely from all this reactive state business if it's not your cup of tea with no problems, because Perseus doesn't use MVC as a *pattern* that you develop in, it uses it as an *architecture* that your code works in. You can use development patterns from 1960 or 2060 if you want, Perseus doesn't mind, it'll just work away in the background and make sure your app's state *just works*.

Perseus also adds a little twist to the usual ideas of app state. If your entire app is represented in its state, then wouldn't it be cool if you could *freeze* that state, save it somewhere, and then boot it back up later to bring your app to exactly where it was before? This is inbuilt into Perseus, and it's still insanely fast. But, if you don't want it, you can turn it off, no problem.
Perseus also adds a little twist to the usual ideas of app state. If your entire app is represented in its state, then wouldn't it be cool if you could *freeze* that state, save it somewhere, and then boot it back up later to bring your app to exactly where it was before? This is built into Perseus, and it's still insanely fast. But if you don't want it, you can turn it off, no problem.

THis does let you do some really cool stuff though, like bringing a user back to exactly where they left off when they log back into your app, down to the last character they typed into an input, with only a few lines of code. (You store a string, Perseus handles the freezing and thawing.)
This does let you do some really cool stuff though, like bringing a user back to exactly where they left off when they log back into your app, down to the last character they typed into an input, with only a few lines of code. (You store a string, Perseus handles the freezing and thawing.)

## Architecture

When you write a Perseus app, you'll usually just define a `main()` function annotated with `#[perseus::main(...)]`, but this does some important things in the background. Specifically, it actually creates three functions: one that returns your `PerseusApp`, and then two new `main()` functions: one for the engine, and another for the browser. That distinction is one you should get used to, because it pervades Perseus. Unfortunately, most other frameworks try to shove this away behind some abstractions, which leads to confusing dynamics about where a function should actually be run. Perseus tries to make this as clear as possible.

Before we can go any further into this though, we'll need to define the *engine*, because it's a Perseus-specific term. Usually, people would refer to the server-side, but this term was avoided for Perseus to make clear that the server is just a single part of the engine. The engine is made up of these components:
Before we can go any further into this though, we'll need to define the *engine*, because it's a Perseus-specific term. Usually, people would refer to the server-side, but this term was avoided for Perseus to make it clear that the server is just a single part of the engine. The engine is made up of these components:

- Builder --- builds your app, generating some stuff in `dist/`
- Exporter --- goes a few steps further than the builder, structuring your app for serving as a flat file structure, with no explicit server
Expand All @@ -59,9 +59,9 @@ So, when we use the `#[perseus::main(...)]` macro, that's creating a function th

What's nice about this architecture is that you can do it yourself without the macro! In fact, if you want to do more advanced things, like setting up custom API routes, this is the best way to go. Then, you would use the `#[perseus::engine_main]` and `#[perseus::browser_main]` annotations to make your life easier. (Or, you could avoid them and do their work yourself, which is very straightforward.)

The key thing here is that you can easily use this more advanced structure to gain greater control over your app, without sacrificing any performance. From here, you can also gain greater control over any part of your app's build process, which makes Perseus practically infinitely customizable to do exactly what you want!
The key thing here is that you can easily use this more advanced structure to gain greater control over your app without sacrificing any performance. From here, you can also gain greater control over any part of your app's build process, which makes Perseus practically infinitely customizable to do exactly what you want!

The upshot of all this is that Perseus is actually creating two separate entrypoints, one for the engine-side, and another for the browser-side. Crucially, both use the same `PerseusApp`, which is a universal way of defining your app's components, like templates. (You don't need to know this, but it actually does slightly different things on the browser and engine-sides itself to optimize your app.)
The upshot of all this is that Perseus is actually creating two separate entrypoints, one for the engine-side and another for the browser-side. Crucially, both use the same `PerseusApp`, which is a universal way of defining your app's components, like templates. (You don't need to know this, but it actually does slightly different things on the browser and engine-sides itself to optimize your app.)

Why do you need to know all this? Because it makes it much easier to understand how to expand your app's capabilities, and it demystifies those macros a bit. Also, it shows that you can actually avoid them entirely if you want to! (Sycamore also has a [builder API](https://sycamore-rs.netlify.app/docs/basics/view#builder-syntax) that you can use to avoid their `view! { .. }` macro too, if you really want.)

Expand Down

0 comments on commit eeccf81

Please sign in to comment.