Migrating from v0.3.x
Perseus v0.4.x added a significant number of breaking changes, as almost the entire Perseus core was rewritten from scratch, along with a full migration to Sycamore v0.8.x, which requires some rewriting of your view code, most of which is covered on the Sycamore website.
- Restructure your
Cargo.toml
to reflect the new dependency-splitting format (which splits engine-side dependencies from those only needed in the browser). See here for an example. Note that this will involve adding a server integration for use, likeperseus-warp
(on which you'll probably want to enable thedflt-server
feature). - Upgrade the Perseus CLI with
cargo install perseus-cli
. - Delete the old
.perseus/
directory (this is no longer needed). - Rename your
lib.rs
file tomain.rs
and delete.perseus/
(it's been removed entirely!). - Change the
#[perseus::main]
attribute on the function inmain.rs
to be#[perseus::main(perseus_axum::dflt_server)]
(replaceperseus_axum
with whatever server integration you decide to use). - Update your view code for Sycamore's new version (mostly including adding a
cx
parameter as the first argument of every function that returns aView<G>
). - Add
.build()
to the bottom of allTemplate::new
calls, and change those toTemplate::build
. - Remove
#[perseus::template_rx]
and#[perseus::template]
entirely. Templates that take reactive state should use#[auto_scope]
now. - Remove lifetimes from templates that take state, as these can be handled by
#[auto_scope]
now. See the#[auto_scope]
docs for more details, including on how to do this manually. - Change all
.template()
calls (onTemplate
, notPerseusApp
) to be.view()
(for templates that take no state),.view_with_unreactive_state()
for unreactive state, or.view_with_state()
for reactive state. - Change
#[make_rx]
to#[derive(Serialize, Deserialize, Clone, ReactiveState)]
. You'll need to deriveUnreactiveState
on unreactive state types as well. - Replace all macros like
#[build_state]
with#[engine_only_fn]
(these have all been combined and simplified). - Replace
RenderFnResult
/RenderFnResultWithCause
with your own error types. For blamed types (i.e. everything other than build paths), your error should be wrapped inBlamedError
. If you don't actually need to return an error, you can return your state directly now, without anyResult
! - Replace uses of
blame_err!
with manually returning aBlamedError
. This API has been dramatically simplified. - Make your header setting functions take a scope
cx
as their first argument. - Replace your
ErrorPages
withErrorViews
(see the documentation of thisstruct
, and thecore/error_views
example for further details). - Update any manual destructuring of your state to use references (e.g.
let content = state.content;
->let content = &state.content;
). - Change
MyStateRx<'b>
to&MyStateRx
(you'll need&'b MyStateRx
if you're not using#[auto_scope]
). - In your
PerseusApp
, change all the times you've provided functions to actually call those functions (e.g..template(crate::templates::index::get_template)
->.template(crate::templates::index::get_template())
). - Change any
#[cfg(target_arch = "wasm32")]
instances to be#[cfg(client)]
, and any#[cfg(not(target_arch = "wasm32"))]
ones to say#[cfg(engine)]
. - Change any head functions that take state to use
.head_with_state()
(the same applies for header setting functions, which can now take state too). - Update your code for any smaller breaking changes that might affect you, as per the changelog.
- Run
cargo update
and thenperseus clean && perseus build
to get everything up to date and ensure that your app works! (This might take a while the first time.)
We realize that this is a mammoth number of breaking changes, and there will be several more if you're a plugin developer. However, Perseus v0.4.0 brings extraordinary levels of performance and ergonomics to Perseus, removing old quirks and streamlining the internals massively. With the introduction of the new capsules system, Perseus is on par with the most powerful frontend frameworks in the world. If you're having any trouble with updating, please do not hesitate to let us know on Discord, and we'll happily help you out as soon as we can!
Note that, in order to make rust-analyzer
etc. work with the new version of Perseus, you'll need to tell it to compile your app for the engine-side by default. You can do this by adding a .cargo/config.toml
file to the root of your project with the following contents:
[build]
rustflags = [ "--cfg", "engine" ]
If you later want to work on a browser-only part of your app, you can just change engine
to client
while you work, and Rust will compile your code accordingly!
IMPORTANT: One of the more subtle things that changed in beta 12 of v0.4.0 is that the path
provided to get_build_state
no longer includes the template path! This means what was once, say, docs/foo
would now just be foo
, which can cause a lot of problems for existing code. This was not a decision taken lightly, but the original choice was made due to routing constraints at the time, which no longer exist, and it has been decided that this will be better for future users. Until you've updated all your code to account for this, you may want to add the following compatibility snippet to the top of your get_build_state
functions:
let path = format!("<template-name>/{}", path);
let path = path.strip_suffix("/").unwrap_or(&path).to_string();
Of course, replace <template-name>
with the name you put in Template::build()
, like docs
, or posts
. The suffix strip is there to handle index pages to prevent trailing forward slashes that might otherwise confuse your application. Eventually, you should adapt your code to work with the new path
system, but this provides a good fallback to at least make your app work until you have the time for that larger change.
If You've Ejected
If you were running Perseus v0.3.x and had ejected, your app's structure is likely to change significantly, as Perseus v0.4.x no longer uses .perseus/
! For example, you can now directly modify the server Perseus runs, allowing you to add your own API routes trivially! (See the custom server example for details.)
The migration process you follow will be highly unique to your app's structure, though most common use-cases should be covered by the custom server example, linked above. If you need any further help, feel free to ask in GitHub discussions or on Discord, and we're happy to help in any way we can!