Skip to content

Commit

Permalink
feat!: ✨ added deployment (#37)
Browse files Browse the repository at this point in the history
* feat: ✨ added support for server to run as standalone binary

This enables realistic production deployments.

* feat: ✨ added ability to deploy with single command

Standalone package folders can be created easily now.

* docs(book): 📝 added docs for deployment

* feat(templates): ✨ switched to `ImmutableStore` from `ConfigManager`

It's currently used in places it shouldn't be, `MutableStore` coming soon

BREAKING CHANGE: removed `ConfigManager` in favor of `ImmutableStore`, replaced `config_manager` with `dist_path` in `define_app!`

* feat: ✨ created `MutableStore` for mutable build artifacts

This replaces `ConfigManager` fully.

BREAKING CHANGE: many function signatures now include `MutableStore`, changes to `dist/` structure, `mutable_store` now in `define_app!`, `RouteInfo` includes `was_incremental_match`

* docs(book): 📝 added docs for new stores system

* refactor(examples): ♻️ refactored perseus idioms to make more sense

Specifically, template functions are now defined inside the `get_template` function.

* docs(book): 📝 updated docs for current state of features

* fix: 🐛 fixed inconsistencies in paths given to build paths vs incremental

Build paths used to get locale as well in path, not anymore.

* chore: 🙈 ignored testing deployments

* fix: 🐛 fixed content being interpolated in head in production

Just a missing `.head.html` rather than `.html`.
  • Loading branch information
arctic-hen7 committed Sep 30, 2021
1 parent 85b6712 commit a8989dd
Show file tree
Hide file tree
Showing 58 changed files with 1,005 additions and 443 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
Cargo.lock
pkg/
9 changes: 5 additions & 4 deletions docs/next/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,17 @@
- [State Amalgamation](./strategies/amlagamation.md)
- [CLI](./cli.md)
- [Ejecting](./ejecting.md)
- [Config Managers](./config-managers.md)
- [Testing](./testing/intro.md)
- [Checkpoints](./testing/checkpoints.md)
- [Fantoccini Basics](./testing/fantoccini-basics.md)
- [Manual Testing](./testing/manual.md)
- [Styling](./styling.md)
- [Stores](./stores.md)
- [Static Exporting](./exporting.md)
- [Deploying](./deploying/intro.md)
- [Static Exporting](./deploying/exporting.md)
- [Server Deployment]()
- [Serverless Deployment]()
- [Server Deployment](./deploying/serverful.md)
- [Serverless Deployment](./deploying/serverless.md)
- [Optimizing Code Size](./deploying/size.md)
- [Migrating from v0.1.x](./updating.md)
***
# Advanced
Expand Down
12 changes: 12 additions & 0 deletions docs/next/src/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ Builds your app in the same way as `build`, and then builds the Perseus server (

You can also provide `--no-build` to this command to make it skip building your app to Wasm and performing static generation. In this case, it will just build the serve rand run it (ideal for restarting the server if you've made no changes).

### `test`

Exactly the same as `serve`, but runs your app in testing mode, which you can read more about [here](./testing/intro.md).

### `export`

Builds and exports your app to a series of purely static files at `.perseus/dist/exported/`. This will only work if your app doesn't use any strategies that can't be run at build time, but if that's the case, then you can easily use Perseus without a server after running this command! You can read more about static exporting [here](./exporting.md).

### `deploy`

Builds your app for production and places it in `pkg/`. You can then upload that folder to a server of your choosing to deploy your app live! You can (and really should) read more about deployment and the potential problems you may encounter [here](./deploying/intro.md).

### `clean`

This command is the solution to just about any problem in your app that doesn't make sense, it deletes the `.perseus/` directory entirely, which should remove any corruptions! If this doesn't work, then the problem is in your code (unless you just updated to a new version and now something doesn't work, then it's probably on us, please [open an issue](https://github.com/arctic-hen7/perseus)!).
Expand Down
5 changes: 0 additions & 5 deletions docs/next/src/config-managers.md

This file was deleted.

3 changes: 2 additions & 1 deletion docs/next/src/define-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ Here's a list of everything you can provide to the macro and what each one does
- `default` -- the default locale of your app (e.g. `en-US`)
- `other` -- a list of the other locales your app supports
- `static_aliases` (optional) -- a list of aliases to static files in your project (e.g. for a favicon)
- `config_manager` (optional) -- a custom configuration manager
- `dist_path` (optional) -- a custom path to distribution artifacts (this is relative to `.perseus/`!)
- `mutable_store` (optional) -- a custom mutable store
- `translations_manager` (optional) -- a custom translations manager

**WARNING:** if you try to include something from outside the current directory in `static_aliases`, **no part of your app will load**! If you could include such content, you might end up serving `/etc/passwd`, which would be a major security risk.
Expand Down
22 changes: 21 additions & 1 deletion docs/next/src/deploying/intro.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Deploying

> **WARNING:** although Perseus is technically ready for deployment, the system is not yet recommended for production! See [here](../what-is-perseus.md#how-stable-is-it) for more details.
Perseus is a complex system, but we aim to make deploying it as easy as possible. This section will describe a few different types of Perseus deployments, and how they can be managed.

*Note: Perseus deployment is still under design and development, so this information in particular is subject to rapid change before v1.0.0.*
## Release Mode

The Perseus CLI supports the `--release` flag on the `build`, `serve`, and `export` commands. When you're preparing a production release of your app, be sure to use this flag!

## `perseus deploy`

If you haven't [ejected](../cli/ejecting.md), then you can prepare your app for deployment with a single command: `perseus deploy`. If you can use [static exporting](../exporting.md), then you should run `perseus deploy -e`, otherwise you should just use `perseus deploy`.

This will create a new directory `pkg/` for you (you can change that by specifying `--output`) which will contain everything you need to deploy your app. That directory is entirely self-contained, and can be copied to an appropriate hosting provider for production deployment!

Note that this command will run a number of optimizations in the background, including using the `--release` flag, but it won't try to aggressively minimize your Wasm code size. For tips on how to do that, see [here](./size.md).

### Static Exporting

If you use `perseus deploy -e`, the contents of `pkg/` can be served by any file host that can handle the [slight hiccup](../exporting.md#file-extensions) of file extensions. Locally, you can test this out with [`serve`](https://github.com/vercel/serve), a JavaScript package designed for this purpose.

### Fully-Fledged Server

If you just use `perseus deploy`, the `pkg/` directory will contain a binary called `server` for you to run, which will serve your app on its own. However, it's important to note that this binary is structured to support either the development configuration of running inside `.perseus/` or the production configuration of running inside `pkg/`, and you have to provide the `PERSEUS_STANDALONE` environment variable to tell it to do the latter. This binary can then be run on any server with a writable filesystem. For more details on this, see the next subsection.
27 changes: 27 additions & 0 deletions docs/next/src/deploying/serverful.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Server Deployment

If your app uses rendering strategies that need a server, you won't be able to export your app to purely static files, and so you'll need to host the Perseus server itself.

You can prepare your production server by running `perseus deploy`, which will create a new directory called `pkg/`, which will contain the standalone binary and everything needed to run it. You should then upload this file to your server and set the `PERSEUS_STANDALONE` environment variable to `true` so that Perseus expects a standalone binary configuration. Note that this process will vary depending on your hosting provider.

## Hosting Providers

As you may recall from [this section](../stores.md) on immutable and mutable stores, Perseus modifies some data at runtime, which is problematic if your hosting provider imposes the restriction that you can't write to the filesystem (as Netlify does). Perseus automatically handles this as well as it can by separating out mutable from immutable data, and storing as much as it can on the filesystem without causing problems. However, data for pages that use the *revalidation* or *incremental generation* strategies must be placed in a location where it can be changed while Perseus is running.

If you're only using *build state* and/or *build paths* (or neither), you should export your app to purely static files instead, which you can read more about doing [here](../exporting.md). That will avoid this entire category of problems, and you can deploy basically wherever you want.

If you're bringing *request state* into the mix, you can't export to static files, but you can run on a read-only filesystem, because only the *revalidation* and *incremental generation* strategies require mutability. Perseus will use a mutable store on the filesystem in the background, but won't ever need it.

If you're using *revalidation* and *incremental generation*, you have two options, detailed below.

### Writable Filesystems

The first of these is to use an old-school provider that gives you a filesystem that you can write to. This may be more expensive for hosting, but it will allow you to take full advantage of all Perseus' features in a highly performant way.

You can deploy to one of these providers without any further changes to your code, as they mimic your local system almost entirely (with a writable filesystem). Just run `perseus deploy` and copy the resulting `pkg/` folder to the server!

### Alternative Mutable Stores

The other option you have is deploying to a modern provider that has a read-only filesystem and then using an alternative mutable store. That is, you store your mutable data in a database or the like rather than on the filesystem. This requires you to implement the `MutableStore` `trait` for your storage system (see the [API docs](https://docs.rs/perseus)), which should be relatively easy. You can then provide this to the `define_app!` macro with the `mutable_store` parameter. Make sure to test this on your local system to ensure that your connections all work as expected before deploying to the server, which you can do with `perseus deploy` and by then copying the `pkg/` directory to the server.

This approach may seem more resilient and modern, but it comes with a severe downside: speed. Every request that involves mutable data (so any request for a revalidating page or an incrementally generated one) must go through four trips (an extra one to and from the database) rather than two, which is twice as many as usual! This will bring down your site's time to first byte (TTFB) radically, so you should ensure that your mutable store is as close to your server as possible so that the latency between them is negligible. If this performance pitfall is not acceptable, you should use an old-school hosting provider instead.
3 changes: 3 additions & 0 deletions docs/next/src/deploying/serverless.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Serverless Deployment

> This strategy of Perseus deployment will be possible eventually, but right now more work needs to be done on support for read-only filesystems before work on this can even be considered.
43 changes: 43 additions & 0 deletions docs/next/src/deploying/size.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Optimizing Code Size

If you're used to working with Rust, you're probably used to two things: performance is everything, and Rust produces big binaries. With Wasm, these actually become problems because of the way the web works. If you think about it, your Wasm files (big because Rust optimizes for speed instead of size by default) need to be sent to browsers. So, the larger they are, the slower your site will be. Fortunately, Perseus only makes this relevant when a user first navigates to your site with its [subsequent loads](../advanced/subsequent-loads.md) system. However, it's still worth optimizing code size in places.

If you've worked with Rust and Wasm before, you may be familiar with `wasm-opt`, which performs a ton of optimizations for you. Perseus does this automatically with `wasm-pack`. But we can do better.

## `wee_alloc`

Rust's memory allocator takes up quite a lot of space in your final Wasm binary, and this can be solved by trading off performance for smaller sizes, which can actually make your site snappier because it will load faster. `wee_alloc` is an alternative allocator built for Wasm, and you can enable it by adding it to your `Cargo.toml` as a dependency:

```toml
wee_alloc = "0.4"
```

And then you can add it to the top of your `src/lib.rs`:

```rust,no_run,no_playground
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
```

With the [basic example](https://github.com/arctic-hen7/perseus/tree/main/examples/basic), we saw improvements from 369.2kb to 367.8kb with `wee_alloc` and release mode. These aren't much though, and we can do better.

## Aggressive Optimizations

More aggressive optimizations need to be applied to both Perseus' engine and your own code, so you'll need to [eject](../ejecting.md) for this to work properly. Just run `perseus eject`, and then add the following to `.perseus/Cargo.toml`:

```toml
[profile.release]
lto = true
opt-level = "z"
```

Then add the same thing to your own `Cargo.toml`. Note that, if this is the only modification you make after ejecting, `perseus deploy` will still work perfectly as expected.

What this does is enable link-time optimizations, which do magic stuff to make your code smaller, and then we set the compiler to optimize aggressively for speed. On the [basic example](https://github.com/arctic-hen7/perseus/tree/main/examples/basic), we say improvements from 367.8kb with `wee_alloc` and release mode to 295.3kb when we added these extra optimizations. That's very significant, and we recommend using these if you don't have a specific reason not to. Note however that you should definitely test your site's performance after applying these to make sure that you feel you've achieved the right trade-off between performance and speed. If not, you could try setting `opt-level = "s"` instead of `z` to optimize less aggressively for speed, or you could try disabling some optimizations.

<details>
<summary>Read this if something blows up in your face.</summary>

As of time of writing, Netlify (and possibly other providers) doesn't support Rust binaries that use `lto = true` for some reason, it simply doesn't detect them, so you shouldn't use that particular optimization if you're working with Netlify.

</details>
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
# Static Exporting

The easiest way to deploy Perseus is as a set of static files, which is supported if your app uses only the _build state_ and _build paths_ strategies, or none at all. If you use _incremental generation_, _revalidation_, or _request state_ in any of your templates though, you can't export your app to static file, because these strategies require a custom server. For these cases, please continue to the rest of this section to learn how to deploy your more complex setup.

However, if your app only needs to run server-side computations at build-time, then you can export it to a set of static files without changing anything, simply by running `perseus export`. This will create a new directory called `.perseus/dist/exported`, the contents of which can be served on a system like [GitHub Pages](https:://pages.github.com). Your app should behave in the exact same way with exporting as with normal serving. If this isn't the case, please [open an issue](https://github.com/arctic-hen7/perseus/issues/new/choose).
Thus far, we've used `perseus serve` to build and serve Perseus apps, but there is an alternative way that offers better performance in some cases. Namely, if your app doesn't need any rendering strategies that can't be run at build time (so if you're only using *build state* and/or *build paths* or neither), you can export your app to a set of purely static files that can be served by almost any hosting provider. You can do this by running `perseus export`, which will create a new directory `.perseus/dist/exported/`, the contents of which can be served on a system like [GitHub Pages](https:://pages.github.com). Your app should behave in the exact same way with exporting as with normal serving. If this isn't the case, please [open an issue](https://github.com/arctic-hen7/perseus/issues/new/choose).

There is only one known difference between the behavior of your exported site and your normally served site, and that's regarding [static aliases](../static-content.md). In a normal serving scenario, any static aliases that conflicted with a Perseus page or internal asset would be ignored, but, in an exporting context, **any static aliases that conflict with Perseus pages will override them**! If you suspect this might be happening to you, try exporting without those aliases and make sure the URL of your alias file doesn't already exist (in which case it would be a Perseus component).

## File Extensions

One slight hiccup with Perseus' static exporting system comes with regards to the `.html` file extension. Perseus' server expects that pages shouldn't have such extensions (hence `/about` rather than `/about.html`), but, when statically generated, they must have these extensions in the filesystem. So, if you don't want these extensions for your users (and if you want consistent behavior between exporting and serving), it's up to whatever system you're hosting your files with to strip these extensions. Many systems do this automatically, though some (like Python's `http.server`) do not.

One of the best systems for testing static exporting on your local machine is the [`serve`](https://github.com/versel/serve) JavaScript package, which can be run from the command-line without touching any JavaScript, and it handles this problem automatically. However, other solutions certainly exist if you don't want any JS polluting your system!
One of the best systems for testing static exporting on your local machine is the [`serve`](https://github.com/vercel/serve) JavaScript package, which can be run from the command-line without touching any JavaScript, and it handles this problem automatically. However, other solutions certainly exist if you don't want any JS polluting your system!
2 changes: 1 addition & 1 deletion docs/next/src/i18n/using.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ In that example, we've imported `perseus::t`, and we use it to translate the `he
That said, there are some cases in which you'll want access to the underlying `Translator` so you can do more complex things. You can get it like so:

```rust,no_run,no_playground
sycamore::context::use_context::<Rc<Translator>>();
sycamore::context::use_context::<perseus::template::RenderCtx>().translator;
```

To see all the methods available on `Translator`, see [the API docs](https://docs.rs/perseus).
Loading

0 comments on commit a8989dd

Please sign in to comment.