diff --git a/docs/next/en-US/SUMMARY.md b/docs/next/en-US/SUMMARY.md index 8f290e93f5..adf9ea0053 100644 --- a/docs/next/en-US/SUMMARY.md +++ b/docs/next/en-US/SUMMARY.md @@ -51,8 +51,7 @@ # Capsules - [Introduction](/docs/capsules/intro) -- [Capsules vs. templates](/docs/capsules/capsules-vs-templates) -- [Delayed widgets](/docs/capsules/delayed) +- [Using capsules](/docs/capsules/using) # Miscellaneous diff --git a/docs/next/en-US/capsules/using.md b/docs/next/en-US/capsules/using.md new file mode 100644 index 0000000000..085060d295 --- /dev/null +++ b/docs/next/en-US/capsules/using.md @@ -0,0 +1,61 @@ +# Using capsules + +Using capsules in your own code involves a few steps. First, you'll want to create a `capsules/` directory in the root of your project (this is just convention), which is just like `templates/`, but (surprise surprise) for capsules. You'll also probably want to bring [`lazy_static`](https://docs.rs/lazy_static/latest/lazy_static) into your project as a dependency so you can use the *referential defin pattern* of capsule definition. This means, rather than having something like a `get_capsule()` function that you use to get your capsule, you create a static reference to it that you can use from anywhere in your program. This is because, unlike templates, capsules get used in more than one place than just `PerseusApp`: you'll also need them where you want to interpolate them into templates. + +## `Capsule` vs. `Template` + +When you're creating a capsule, there's a new type to get familiar with, called [`Capsule`](=prelude/struct.Capsule@perseus), which works similarly to the templates you're used to, but it's also slightly different. Basically, you want to start with a `Template`, define all your state generation stuff on there (but not your views), then pass that to `Capsule::build()` *without* alling `.build()` on it, and then specify your capsule-specific properties on `Capsule`. It's best to give an example: + +``` +{{#include ../../../examples/core/capsules/src/capsules/greeting.rs}} +``` + +This is a very simple capsule analogous to the *Hello world!* template we built in the first tutorial, but it shows how capsules generally work. You have the definition of the capsule as a static reference up the top (here using a `get_capsule()` function, but you can do whatever you like here). Notice the use of [`PerseusNodeType`](=prelude/type.PerseusNodeType@perseus), which denotes the correct rendering backend based on system circumstances (namely the presence of the `hydrate` feature and the `engine`/`client` flags), since generics aren't allowed in lazy statics. This will be reconciled using internal transmutes with whatever `G` you use in your templates (this is unsafe code internally, but your app will panic if it thinks it will undergo any undefined behavior, and this should really never happen unless you're doing some extremely wacky things). + +Notice that `greeting_capsule`, our view function, is almost identical to that for a template, except it takes a third argument `props` for the properties, which just need to be `Clone`, and that also makes the second generic on `Capsule` itself. + +In the `get_capsule` function, you can see we're creating a `Template` with the name of the capsule (which will be gated behind a special internal prefix, so it won't conflict with any of your templates), and then we declare state generation functions and the like on there (like [build-time state](:state/build)). This is *identical* to a full template. + +Then, we set the *fallback function*, which is a view that will be used while widgets that this capsule produces are still being fetched: this is usually a loader or something similar, but here we're just using `.empty_fallback()` to use an empty view. Any capsules without fallback functions will fail the Perseus build process. + +We then use `.view_with_state()`, which might look exactly the same as the template one, but remember that capsule functions take an extra argument! They also have completely different internal logic compared to templates, so make sure you're defining your views on the `Capsule` rather than the `Template`! (If you make a mistake here, your capsules will simply be blank, rather than causing untold internal errors.) + +Finally, we call `.build()` to turn that all into a proper `Capsule`. + +## Using widgets + +Here's an example of a page that uses some widgets: + +``` +{{#include ../../../examples/core/capsules/src/templates/index.rs}} +``` + +This template uses two widgets: one called `LINKS`, and another called `WRAPPER` (which is a wrapper over the `GREETING` capsule we defined in the previous example). To use these, we interpolate them like variables into a Sycamore `view!` using the `.widget()` function, which takes three arguments: the Sycamore scope, *the path to the widget*, and the properties. For capsules that have no properties, we use the unit type `()`. + +Note that there is no place where we have to declare all the widgets a page uses, and they can even be state dependent (e.g. only if the state property `foo` is set to `5` do we render a widget from the `BAR` capsule). Perseus will figure out which widgets a page uses by actually rendering it. This also means that you can nest widgets (as in the `WRAPPER` capsule in the above example), but don't do too much nesting, since the best Perseus can do is build each layer one at a time, meaning, if you have five layers of nesting, it will take five sequential re-renders to render your whole page on the engine-side (a similar thing goes for fetching on the client-side). + +### Widget paths + +That second argument to the capsule's `.widget()` function is by far the most important, and this is why we've emphasized that idea of **template + page = state**. Based on that, and Perseus' routing systems, any template `foo` will render pages under `/foo/` on your website. So this argument is what goes under `/foo/`. Let's say we have a capsule `ALPHABET` that renders one widget for every letter of the Latin alphabet: we might put it `/a` as our path if we wanted to render the `__capsule/alphabet/a` widget, or `/x` if we wanted to render `__capsule/alphabet/x`. (That `__capsule` prefix is applied internally, and you'll only need it if you're manually querying Perseus' API.) + +In the above example, we used the empty string to denote the index page, because the capsules we're using only render one widget each. + +if you want to see a more advanced example of a capsule that uses incremental generation to build widgets, check out [this code]. + +*Note: while it might seem extremely weird, there is nothing to stop you from reactively changing the widgets you render on the client-side, as in [this example].* + +## Delayed widgets + +As explained earlier, Perseus automatically collates all widgets into one HTMl page before serving an *initial load*, for speed, but sometimes this is undesirable. Sometimes, no matter what, you want one section of your page to be loaded after the rest of the page is ready, usually to improve page load times by delaying the load of a heavy section. This can be done trivially by replacing `.widget()` with `.delayed_widget()`. Yep, that's all. + +*Note: currently, delayed widgets don't work with hydration, which a bug we're working on. For now, you'll need to disable the `hydrate` feature flag if you want to use them.* + +## Rescheduling + +One of the trickiest parts of the capsule system to build internally centered around this problem: what should Perseus do when you create a page that uses build state, and make that use a widget that uses request state? The page would normally be rendered at build-time, but it can't be, because it's waiting on a widget. In these cases, Perseus needs to *reschedule* the build of such pages to request-time, which it needs your permission to do (since this will reduce performance). When you're building your app, you should keep this in the back of your mind, and be prepared for rescheduling errors that might arise in the build process: these can always be solved by adding `.allow_rescheduling()` to the definition of a template that fits these properties. Do *not* pre-emptively add `.allow_rescheduling()`, wait for the actual error to make sure you need it (there are some cases where Perseus can optimize things). + +One notable instance where this isn't necessary is in incremental generation. For example, let's say you have a capsule `number` that has a build paths listing of `1`, `2`, and `3`, but it can incrementally render any number. Then let's say you have a page called `four` that uses `number/4` --- typically, Perseus would wait until somebody requested the `foo/4` widget to render it, but here it's being very clearly used at build-time, so Perseus will figure out what you mean and just render it at build-time anyway. This means you don't have to laboriously keep all your build paths in sync, which can lead to faster development and fewer maintainence headaches. + +## Support + +The Perseus capsules system is not only very new, it is a completely novel architecture, so there are bound to be bugs and idiosyncracies that can be improved. If you're having problems, even if you don't think they're a bug, please let us know through [a GitHub discussion], or [on Discord] --- every little bit of feedback helps us improve Perseus and make it easier for you to develop lightning-fast apps! If you do reckon you've found a bug, or if you'd like to request a new feature, please open an issue [on GitHub] and let us know! diff --git a/docs/next/en-US/reference/faq.md b/docs/next/en-US/faq.md similarity index 100% rename from docs/next/en-US/reference/faq.md rename to docs/next/en-US/faq.md diff --git a/docs/next/en-US/first-app/deploying.md b/docs/next/en-US/first-app/deploying.md index 7e925dca81..8ea04fdcf1 100644 --- a/docs/next/en-US/first-app/deploying.md +++ b/docs/next/en-US/first-app/deploying.md @@ -32,6 +32,16 @@ When it's done, this command wil produce a `pkg/` folder in the root of your pro Obviously, you probably want to host your app in production on a different address, like `0.0.0.0` (network-speak for "host this everywhere so everyone who comes to my server can find it"), and perhaps on port `80`. Note that Perseus doesn't handle HTTPS at all, and you'll need to do this with a reverse proxy or the like (which comes built-in to most servers these days). You can set the host and port with the `PERSEUS_HOST` and `PERSEUS_PORT` environment variables. +### Optimizations + +When you deploy your Perseus app, there are two separate main binaries that are produced: the Wasm bundle, and the engine binary (the latter won't exist if you use export deployment though). What you want to do is optimize the engine binary for speed, since it's running your server, and the Wasm bundle for *size*: the reason is because Wasm is already extremely fast, and the main impediment to speed in the browser is how long it takes to load the Wasm bundle from the server. *Smaller bundle = faster load.* (But remember that this is only for making your pages interactive, the user will see content straight away!) + +Most of these optimizations are all applied automatically in `perseus deploy`, but they can be tweaked if you like by setting some of the flags on the CLI (which you can see with `perseus deploy --help`). These will allow you to apply different optimization settings to suit your needs. + +One thing you may want to do is replace Rust's default allocator (thing in charge of your app's memory) with something slower but smaller. There are two options here: [`wee_alloc`] (which has memory leaks, and is now unmaintained), and the newer (but largely untested) [`lol_alloc`]. Whatever you do, make sure you only use these with `#[cfg(client)]` to make sure they don't get used for your server as well! (Since that would *massively* slow down your site.) + +For more information on optimizing Wasm bundle sizes, see [here](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size). + ## Export deployment However, there's actually a simpler way of deploying this app in particular. Because we aren't using any features that need a server (e.g. we're generating state at build-time, not request-time, so all the server is doing is just passing over files that it generated when we built the app), we can *export* our app. You can try this for development with `perseus export -s` (the `-s` tells Perseus to spin up a file server automatically to serve your app for you). In production, use `perseus deploy -e` to make `pkg/` contain a series of static files. If you have `python` installed on your computer, you can serve this with `python -m http.server -d pkg/`. The nice thing about exported apps is that they can be sent to places like [GitHub Pages], which will host your app for free. In fact, this whole website is exported (because it's all static documentation), and hosted on exactly that service! diff --git a/docs/next/en-US/reference/compilation-times.md b/docs/next/en-US/fundamentals/compilation-times.md similarity index 95% rename from docs/next/en-US/reference/compilation-times.md rename to docs/next/en-US/fundamentals/compilation-times.md index 0f05bbca75..2d812b4305 100644 --- a/docs/next/en-US/reference/compilation-times.md +++ b/docs/next/en-US/fundamentals/compilation-times.md @@ -14,4 +14,4 @@ The easiest way to get Perseus to use this, instead of the usual `cargo`, is to **Remember:** do NOT use `cargo-clif` in production! -After applying all the optimizations herein, a testing benchmark of measuring the time taken to run `perseus build` with the bleeding-edge version of the CLI in development mode on the `basic` example, changing a hardcoded state property, went from taking 28 seconds with the stable compiler and no target directory separation to just 7 seconds, when Cranelift and nightly were used along with the target directory separation now inbuilt into Perseus. In other words, you can cut Perseus' compile times by 75%! (And this was deliberately on a fairly old laptop with other programs running in the background to mimic a realistic setup.) +After applying all the optimizations herein, a testing benchmark of measuring the time taken to run `perseus build` with the bleeding-edge version of the CLI in development mode on the `basic` example, changing a hardcoded state property, went from taking 28 seconds with the stable compiler and no target directory separation to just 7 seconds, when Cranelift and nightly were used along with the target directory separation now inbuilt into Perseus. In other words, you can **cut Perseus' compile times by 75%**! (And this was deliberately on a fairly old laptop with other programs running in the background to mimic a realistic setup.) diff --git a/docs/next/en-US/reference/migrating.md b/docs/next/en-US/migrating.md similarity index 100% rename from docs/next/en-US/reference/migrating.md rename to docs/next/en-US/migrating.md diff --git a/docs/next/en-US/reference/architecture.md b/docs/next/en-US/reference/architecture.md deleted file mode 100644 index 8d9527295c..0000000000 --- a/docs/next/en-US/reference/architecture.md +++ /dev/null @@ -1 +0,0 @@ -# Architecture Details diff --git a/docs/next/en-US/reference/deploying.md b/docs/next/en-US/reference/deploying.md deleted file mode 100644 index 751a53ad25..0000000000 --- a/docs/next/en-US/reference/deploying.md +++ /dev/null @@ -1,44 +0,0 @@ -# Deploying - -When you've built your app, and you're ready to go to production with it, Perseus provides some nifty tools to make your life easy. First off, you'll notice that all your files are sequestered away in `dist/`, which is all very well for keeping a ton of cached stuff out of your way, but not very useful for getting production binaries! - -When you're ready for production, you should run `perseus deploy`, which will build your entire app in release mode (optimizing for size in the browser and speed on the server, which we'll return to), which will take quite a while. This is a good time to make yourself a beverage of some form. When it's done, you'll get a `pkg/` folder with some stuff inside. The main thing is a file `pkg/server`, which is a binary that will run your app's server, using the rest of the stuff in there for all sorts of purposes. Unless you really know what you're doing, you shouldn't add files here or rearrange things, because that can send the production server a little crazy (it's very particular). - -If you don't need a server for your app, you can use `perseus deploy -e`, which will produce a set of static files to be uploaded to your file host of choice. - -## 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 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. - -If you're getting into real strife with your bundle sizes though, you can, theoretically, split out your app into multiple components by literally building different parts of your website as different apps. This should be an absolute last resort though, and we have never come across an app that was big enough to need this. (Remember that Perseus will still give your users a page very quickly, it's just the interactivity that might take a little longer --- as in a few milliseconds longer.) - -Very usefully, the Perseus CLI automatically applies several optimizations when you build in release mode. Specifically, Cargo's optimization level is set to `z`, which means it will aggressively optimize for size at the expense of speed, which actually means a faster site, due to faster load times for the Wasm bundle. Additionally, `codegen-units` is set to `1`, which slows down compilation with `perseus deploy`, but both speeds up, and reduces the size of, the final bundle. - -Notably, these optimizations are enabled through the `RUSTFLAGS` environment variable on the Wasm build, and only in release-mode (e.g. `perseus deploy`). If you want to tweak these changes, you can directly override the value of that environment variable in this context (i.e. you can apply your own optimization settings) by setting the `PERSEUS_WASM_RELEASE_RUSTFLAGS` environment variable. This takes the same format as `RUSTFLAGS`, and its default value is `-C opt-level=z -C codegen-units=1`. - -*Note: the reason these optimizations are applied through `RUSTFLAGS` rather than `Cargo.toml` is because Cargo doesn't yet support target-specific release profiles, and we only want to optimize for size on the browser-side. Applying the same optimizations to the server would slow things down greatly!* - -The next thing you can do is switch to `wee_alloc`, an alternative allocator designed for the web that produces less efficient, but smaller bundles. Again though, that lower efficiency is barely noticeable, while every kilobyte you can shave off the bundle's size leads to a notably faster load speed. Importantly, you still want to retain that efficiency on the server, so it's very important to only use `wee_alloc` on the browser-side, which you can do by adding the following to the very top of your `lib.rs`: - -```rust -#[cfg(target_arch = "wasm32")] -#[global_allocator] -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; -``` - -To make this work, you should also add the following to your `Cargo.toml` under the `[target.'cfg(target_arch = "wasm32")'.dependencies]` section (for browser-only dependencies): - -```toml -wee_alloc = "0.4" -``` - -Finally, for another small cut to bundle sizes, you can set `wasm-opt`, a tool that `wasm-pack` runs automatically (and the Perseus CLI runs `wasm-pack`) to optimize for size by adding the following to your `Cargo.toml`: - -```toml -[package.metadata.wasm-pack.profile.release] -wasm-opt = [ "-Oz" ] -``` - -You can find more information about optimizing Wasm bundle sizes [here](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size). diff --git a/docs/next/en-US/reference/exporting.md b/docs/next/en-US/reference/exporting.md deleted file mode 100644 index 24a9614353..0000000000 --- a/docs/next/en-US/reference/exporting.md +++ /dev/null @@ -1,13 +0,0 @@ -# Static Exporting - -There are two ways to run a Perseus app: with a server, and without a server. This might seem odd, how could you run an app without a server? Well, you can't really. But, you can *export* your app to static files and then use a stock server to just host those for you. For example, [GitHub Pages]() is a hosting service free for open-source projects that can host static files, and that's what this website runs on! There's no Perseus server behind this, just static files that you could serve with a Python script if you wanted to. - -Most of Perseus' features don't actually need a server, though some do. Specifically, if your app uses incremental generation, revalidation, or request state, attempting to export your app will fail. - -But, if your app doesn't use any of those, you can run `perseus export` and that will export your app to a series of static files! For convenience, `perseus export -s` will spin up a server built into the CLI so you can see what everything looks like. When you're ready to go to production, you can run `perseus deploy -e` to get a `pkg/` directory with your static files, which can be sent off to any file hosting platform! - -The one big caveat with this approach is error pages. When you're using Perseus's server, it's smart enough to use your error pages when it undergoes a 404, 500, 418, etc. error, but a run-of-the-mill static file server will be clueless (this goes for the development server from `perseus export -s` as well). Usually, you'll need to explicitly provide something like `404.html` as a file to tell these servers what to provide to users. However, confusingly, your error pages will work in some circumstances. Specifically, if the user clicks a link inside your app that goes to a page that doesn't exist, this involves asking for a page from the server in the background, and the app will automatically return your error page from within itself in this case. If you go to a nonexistent page from *outside* your app though, you'll get some stock error page that's got nothing to do with Perseus. This might be hard to understand, which is why it's best to do the following. - -You can solve this problem by exporting your error pages to static files with the `perseus export-error-page --code --output ` command, replacing `` with the code you want to export for (e.g. `404`) and `` with where you want to put the file. These won't work at all with the development server, which isn't designed to handle error pages (yet), but a production file server should manage this fine. (Your mileage may vary depending on the hosting provider, so it's best to check first!) - -*Note: apps using exporting only should see [these examples]() for how to avoid having to import a server in `Cargo.toml`.* diff --git a/docs/next/en-US/reference/hydration.md b/docs/next/en-US/reference/hydration.md deleted file mode 100644 index 14f013995a..0000000000 --- a/docs/next/en-US/reference/hydration.md +++ /dev/null @@ -1 +0,0 @@ -# Hydration diff --git a/docs/next/en-US/reference/i18n.md b/docs/next/en-US/reference/i18n.md deleted file mode 100644 index 798fcef772..0000000000 --- a/docs/next/en-US/reference/i18n.md +++ /dev/null @@ -1,15 +0,0 @@ -# Internationalization - -Internationalization, or *i18n* for short, is the process of making your app available in many languages, something Perseus supports out of the box! - -Usually, i18n is done in one of two ways: by having subdomains for each different locale (e.g. `en.example.com`, `de.example.com`), or by having each locale have a separate top-level route (e.g. `example.com/en`, `example.com/de`). Perseus favors the latter approach, which requires less routing overhead (you don't have to manage subdomains), and is generally easier to set up. - -The process of i18n is mostly behind-the-scenes in Perseus, but what it involves at heart is this: each template is built once for each locale, with a different language parameter provided. That parameter allows you to, in your code, detect the locale being used, and provide the right translation of your page's content. Perseus also takes this one step further by providing an inbuilt `TranslationsManager` system, which is used to find translations in the `translations/` folder at the root of your project, which can then be interpolated into your page with the `t!` macro. All examples are available [here](). - -Specifically, Perseus uses the [Fluent](https://projectfluent.org) translation system, which provides a file format with full support for managing variable interpolation, plurals, genders, etc. The `t!` macro allows working with most of these features, though for some more advanced use-cases you'll need to drill down into the `Translator` instance itself, an example of which can be found [here](). - -Not everyone appreciates Fluent though, and there are plenty of other translations systems that exist today. Perseus manages translators on a feature-flag system (so you enable `translator-fluent` to use the default Fluent system), which means more translators can be built into Perseus without any cost to bundle sizes. Currently, only Fluent is supported, though we're happy to accept [PRs]() or [issues]() implementing or proposing more systems! - -The last thing to understand about Perseus' approach to i18n is how we manage translations. You'll store your translations for each locale somewhere like `translations/en-US.ftl` (from the root of your project), but this isn't always the ideal system. Sometimes, for example, you'll want to fetch translations from a database instead, if they're being regularly updated. This can be done by using an alternative to `FsTranslationsManager`, as long as it implements `TranslationsManager`. An example for this can be found [here](). Note that translations will be fetched extremely regularly, so it's generally not recommended to use high-latency managers in server-based applications. If you use `perseus export`, then all translations are automatically hardcoded, though `perseus serve` will fetch them all as it starts up, caching them. (You should never update translations without rebuilding your app, as this could lead to unexpected results.) The translations that are cached immediately can be changed as per [this example](). - -*Note for contributors: there is a `struct ClientTranslationsManager` also present in the codebase, which is responsible for caching translations in the browser. It is not customizable, and has no relation to the `trait TranslationsManager` used on the engine-side.* diff --git a/docs/next/en-US/reference/initial_subsequent_loads.md b/docs/next/en-US/reference/initial_subsequent_loads.md deleted file mode 100644 index f117af1458..0000000000 --- a/docs/next/en-US/reference/initial_subsequent_loads.md +++ /dev/null @@ -1,25 +0,0 @@ -# Initial vs. Subsequent Loads - -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 - -The main thing to understand about initial loads is that they have to send the user *everything*: HTML, the Wasm bundle, etc. After this is all in the user's browser, Perseus can take over the routing process to optimize for performance by only fetching what it needs. First, we need to get everything into the user's browser though. - -At the level of HTTP, an initial load request looks as you might expect. Requesting a page at `/about` requests `/about`, and the server compiles a full HTML file with everything the user needs, sending it to them. For exported apps, these files are compiled when you build your app (which is why you can't use request-time state in exporting, there's no way to update the state in the precompiled HTML before it goes to the client). - -This HTML file has in it a prerendered version of the page, meaning the user will see content straight away, even though the Wasm bundle might take a moment longer to load, after which time the page will become reactive, and you can click buttons, etc. - -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 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. - -## Subsequent Loads - -Once the user's browser has the Wasm bundle, every time they go to a new page, we don't need to fetch that bundle again, or a whole lot actually. We don't even need the HTML scaffold --- just the page's HTML content, its ``, and its state. While you may see a transition from, say, `/` to `/about`, in reality that's just superficial, and no request to `/about` has been made. In fact, a request to somewhere in `/.perseus/` has been made, which will return a JSON object with exactly what we need, minimizing load times between pages, and meaning your browser has to do no more work. From its perspective, we haven't actually moved to a new page. - -This is the approach of *single-page apps*, which aren't really just one page, but they use a routing approach like this for performance. Unfortunately, SPAs have a whole host of other problems caused by this routing, all of which Perseus ims to solve. If you find any problems with our subsequent loads system, please [open an issue](https://github.com/arctic-hen7/perseus/issues/new/choose)! - -*Note: currently, scroll positions are not preserved by the subsequent load system, though this is an upstream issue in Sycamore currently being worked on.* diff --git a/docs/next/en-US/reference/live-reloading-and-hsr.md b/docs/next/en-US/reference/live-reloading-and-hsr.md deleted file mode 100644 index fffd00dc08..0000000000 --- a/docs/next/en-US/reference/live-reloading-and-hsr.md +++ /dev/null @@ -1,17 +0,0 @@ -# Live Reloading and HSR - -When you develop with Perseus, you can add the `-w` flag to either `perseus serve` or `perseus export` to automatically rebuild your app whenever you change any code in your project. When you do, any browsers connected to the development version of your app will also be automatically reloaded, which allows for a more rapid development cycle. (If you want faster compile times, use the nightly channel of Rust.) - -This also involves using *hot state reloading* (HSR), a world first in the non-JavaScript world pioneered by Perseus. This is very similar to *hot module reloading* (HMR) in JavaScript frameworks, which only changes the bare minimum amount of code necessary to let you preview your changes, meaning the state of your app is kept. - -But what does that actually mean? Well, let's take a simple example. Imagine you're working on a form page that has twelve inputs that all need to be filled out. With HMR, most changes to your code will lead to small substitutions in the browser because of the way JS can be chunked into many small files --- your inputs into the form are preserved even across code changes, which is extremely helpful! - -As you may know, Perseus has the concept of state freezing and thawing inbuilt, which allows you to turn the entire state of your app into a string and then restore your app to a single point of interaction from that, which would allow you to take a user back to exactly where they were after they logged back into your app, for example. - -In development, this system is applied automatically to save your app's state to a string in your browser's storage automatically just after it's rebuilt, and this is then restored after the reload, meaning you're taken back to exactly where you were before you made the code change! - -Of course, there are some cases in which this isn't possible --- namely when you change the data model of your app. So, if you add new parameters to the current page's state, Perseus won't be able to process it, and the previous state will be dumped. If you change the data model for another page though, things will still work, until you go to that page, because of the incremental nature of thawing (something you almost never need to care about). Very occasionally, this can lead to odd behavior, which is immediately fixed by simply reloading the page. - -So, in summary, because Wasm can't be chunked, HMR can't be implemented for Wasm projects, including Perseus ones, so we invented a new way of achieving the same results grounded in the state-based architecture of Perseus, meaning you can easily develop complex flows in your app without losing state every time you change some code. - -*Note: if you're a developer using another Wasm framework, and you'd like to implement HSR yourself, hop over to our [Discord channel on the Sycamore server](https://discord.com/invite/GNqWYWNTdp) if you want to discuss implementation details. All of this is open-source, and we'd be thrilled if HSR were more widely adopted in the Wasm community, to improve the developer experience of all!* diff --git a/docs/next/en-US/reference/plugins.md b/docs/next/en-US/reference/plugins.md deleted file mode 100644 index 4d3b30fd2e..0000000000 --- a/docs/next/en-US/reference/plugins.md +++ /dev/null @@ -1,15 +0,0 @@ -# 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 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. - -However, for tasks that involve injecting into Perseus' build system, a plugin is usually the right tool for the job. Let's say for example that you wanted to, before building the app, identify all Sass files that were being imported in the index view and compile them to CSS. You could do this by first parsing the index view, and then compiling everything at build-time. This is doable with plugins, though the mechanics of this particular example are fairly complex. - -In the general case, you can do most things in Perseus by playing around with the code that's exposed to you. If you just want to add something extra inside Perseus' internal processes though (e.g. adding a copyright header to all exported files), this is done with a plugin. If you're unsure, you can always ask in [a discussion]() or on [our Discord channel on the Sycamore server](). - -Perseus' plugins are based on *actions*, which you can make your plugin use to execute arbitrary code, as per [these examples](). There are three types of actions: functional, control, and tinker. Functional actions can have many plugins connected to them (e.g. adding more templates). Control actions can have just one plugin connected to them (e.g. modifying the index view). Tinker plugins are weird, they're executed on the special command `perseus tinker`, and they were originally designed to let people modify the code inside `.perseus/` (that legacy hidden folder), but now they're just...a thing. We haven't thought of any particular use-case for them yet, but there's not really any downside in having them, and, who knows, you might one day discover that a very particular niche application requires that extra step of explicitly executing `perseus tinker` to modify stuff. As for what those tinker plugins can do: literally anything. They're given the entire filesystem and they can roam free. Heck, you could use a tinker plugin to install an application on your computer, if you really wanted to! (And that's why you should only ever use trusted plugins!) - -On the note of security, plugins are extremely powerful. They can execute arbitrary code, and so can do basically whatever they want to your system. We have a list of publicly available plugins [here]() that are accompanied by little badges that indicate review by the Perseus dev team. Usually, those ones at least will be safe to use, though we strongly recommend reviewing the code of the plugins you use yourself, as we do NOT review each new version, and we do NOT keep track of changes to plugin maintainership. In other words, we take no responsibility whatsoever for anything that goes wrong when using a plugin --- make sure you trust the plugins you use! - -All examples of plugin usage are available [here](). diff --git a/docs/next/en-US/reference/router.md b/docs/next/en-US/reference/router.md deleted file mode 100644 index 0762636127..0000000000 --- a/docs/next/en-US/reference/router.md +++ /dev/null @@ -1,38 +0,0 @@ -# Router - -Most of the time, you will never have to worry about how Perseus' router works. However, if you're working on a new server integration, or something else very low-level, you might encounter some thorns to do with how Perseus figures out which page to render. This might seem like a simple problem. The user requests `/about`? Render the `about` template. Now introduce [incremental generation](:reference/state-generation). Now it's a little more complex. For clarity, this page will outline the way Perseus' routing algorithm actually works internally. - -If you're just building apps with Perseus, you shouldn't have to read this, it's more for those working with the internals. - -Note that this algorithm is executed on the client-side for _subsequent loads_ and on the server-side for _initial loads_ (see [here](:reference/initial_subsequent_loads) for details on those). - -Also note that Perseus' routing algorithm is based on a file called `render_conf.json`, which is stored in `dist/`. Importantly, this is stored in memory by the server, and it's interpolated directly into the HTML sent to the user's browser. (Meaning apps with *very\** large numbers of pages should consider incremental generation even if their build times are fine, since it may actually improve load times by a little. Take a look at the `