Skip to content

Commit

Permalink
docs: wrote some sections of the docs
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Jun 28, 2022
1 parent a491a1a commit c7a9f09
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 0 deletions.
16 changes: 16 additions & 0 deletions docs/next/en-US/reference/deploying.md
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
# 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 aggreassively 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.)

However, there are some easy things you can do to make your Wasm bundles much smaller. TODO
12 changes: 12 additions & 0 deletions docs/next/en-US/reference/exporting.md
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
# 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 <http-code> --output <output>` command, replacing `<http-code>` with the code you want to export for (e.g. `404`) and `<output>` 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`.*
14 changes: 14 additions & 0 deletions docs/next/en-US/reference/i18n.md
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
# 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.*
14 changes: 14 additions & 0 deletions docs/next/en-US/reference/plugins.md
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
# 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 customizabel 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]().

0 comments on commit c7a9f09

Please sign in to comment.