Skip to content

Commit a8989dd

Browse files
authored
feat!: ✨ added deployment (#37)
* 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`.
1 parent 85b6712 commit a8989dd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1005
-443
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/target
22
Cargo.lock
3+
pkg/

docs/next/src/SUMMARY.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,17 @@
2929
- [State Amalgamation](./strategies/amlagamation.md)
3030
- [CLI](./cli.md)
3131
- [Ejecting](./ejecting.md)
32-
- [Config Managers](./config-managers.md)
3332
- [Testing](./testing/intro.md)
3433
- [Checkpoints](./testing/checkpoints.md)
3534
- [Fantoccini Basics](./testing/fantoccini-basics.md)
3635
- [Manual Testing](./testing/manual.md)
3736
- [Styling](./styling.md)
37+
- [Stores](./stores.md)
38+
- [Static Exporting](./exporting.md)
3839
- [Deploying](./deploying/intro.md)
39-
- [Static Exporting](./deploying/exporting.md)
40-
- [Server Deployment]()
41-
- [Serverless Deployment]()
40+
- [Server Deployment](./deploying/serverful.md)
41+
- [Serverless Deployment](./deploying/serverless.md)
42+
- [Optimizing Code Size](./deploying/size.md)
4243
- [Migrating from v0.1.x](./updating.md)
4344
***
4445
# Advanced

docs/next/src/cli.md

+12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ Builds your app in the same way as `build`, and then builds the Perseus server (
1414

1515
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).
1616

17+
### `test`
18+
19+
Exactly the same as `serve`, but runs your app in testing mode, which you can read more about [here](./testing/intro.md).
20+
21+
### `export`
22+
23+
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).
24+
25+
### `deploy`
26+
27+
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).
28+
1729
### `clean`
1830

1931
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)!).

docs/next/src/config-managers.md

-5
This file was deleted.

docs/next/src/define-app.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ Here's a list of everything you can provide to the macro and what each one does
2525
- `default` -- the default locale of your app (e.g. `en-US`)
2626
- `other` -- a list of the other locales your app supports
2727
- `static_aliases` (optional) -- a list of aliases to static files in your project (e.g. for a favicon)
28-
- `config_manager` (optional) -- a custom configuration manager
28+
- `dist_path` (optional) -- a custom path to distribution artifacts (this is relative to `.perseus/`!)
29+
- `mutable_store` (optional) -- a custom mutable store
2930
- `translations_manager` (optional) -- a custom translations manager
3031

3132
**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.

docs/next/src/deploying/intro.md

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
# Deploying
22

3+
> **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.
4+
35
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.
46

5-
*Note: Perseus deployment is still under design and development, so this information in particular is subject to rapid change before v1.0.0.*
7+
## Release Mode
8+
9+
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!
10+
11+
## `perseus deploy`
12+
13+
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`.
14+
15+
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!
16+
17+
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).
18+
19+
### Static Exporting
20+
21+
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.
22+
23+
### Fully-Fledged Server
24+
25+
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.

docs/next/src/deploying/serverful.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Server Deployment
2+
3+
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.
4+
5+
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.
6+
7+
## Hosting Providers
8+
9+
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.
10+
11+
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.
12+
13+
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.
14+
15+
If you're using *revalidation* and *incremental generation*, you have two options, detailed below.
16+
17+
### Writable Filesystems
18+
19+
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.
20+
21+
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!
22+
23+
### Alternative Mutable Stores
24+
25+
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.
26+
27+
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.

docs/next/src/deploying/serverless.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Serverless Deployment
2+
3+
> 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.

docs/next/src/deploying/size.md

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Optimizing Code Size
2+
3+
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.
4+
5+
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.
6+
7+
## `wee_alloc`
8+
9+
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:
10+
11+
```toml
12+
wee_alloc = "0.4"
13+
```
14+
15+
And then you can add it to the top of your `src/lib.rs`:
16+
17+
```rust,no_run,no_playground
18+
#[global_allocator]
19+
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
20+
```
21+
22+
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.
23+
24+
## Aggressive Optimizations
25+
26+
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`:
27+
28+
```toml
29+
[profile.release]
30+
lto = true
31+
opt-level = "z"
32+
```
33+
34+
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.
35+
36+
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.
37+
38+
<details>
39+
<summary>Read this if something blows up in your face.</summary>
40+
41+
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.
42+
43+
</details>
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
# Static Exporting
22

3-
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.
4-
5-
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).
3+
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).
64

75
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).
86

97
## File Extensions
108

119
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.
1210

13-
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!
11+
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!

docs/next/src/i18n/using.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ In that example, we've imported `perseus::t`, and we use it to translate the `he
1515
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:
1616

1717
```rust,no_run,no_playground
18-
sycamore::context::use_context::<Rc<Translator>>();
18+
sycamore::context::use_context::<perseus::template::RenderCtx>().translator;
1919
```
2020

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

0 commit comments

Comments
 (0)