Skip to content

Commit

Permalink
save
Browse files Browse the repository at this point in the history
  • Loading branch information
levkk committed Oct 31, 2024
1 parent 5fd9c30 commit 18f07b0
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.md.bak
1 change: 1 addition & 0 deletions docs/docs/.pages
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ nav:
- 'views'
- 'configuration.md'
- 'background-jobs'
- 'user-guides'
- '...'
36 changes: 36 additions & 0 deletions docs/docs/user-guides/hot-reload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Hot reload

Hot reload, also known as hot module replacement (HMR), is a technique for automatically replacing frontend components that changed during local development, without the developer having to reload the page manually.
While Rwf [templates](../views/templates/index.md) don't use JavaScript frameworks like React or Vue, they do support being reloaded automatically.


## Enable hot reload

To enable template hot reloading, make sure your application is using [Turbo Streams](../views/turbo/streams.md). The page refresh event is delivered from the server using a WebSocket connection.

Current hot reload implementation works best if you are storing your templates in one directory, e.g. `templates`. To enable HMR, launch it before launching the HTTP server:

```rust
use rwf::hmr::hmr;

use std::path::PathBuf;

#[tokio::main]
async fn main() {
// Enable HMR notifications for any changes
// to the `templates` directory.
hmr(PathBuf::from("templates"));

/* ... */
}
```

When editing templates with your favorite text editor, Rwf will send an event via the Turbo Stream connection which will reload the page every time a template file is saved. Since Turbo makes page reloads seamless, this simulates the behavior of HMR used by frameworks like React or Vue.

### Debug only

HMR only makes sense in development, so the functionality is available in `debug` builds which are used by default when you use `cargo run`. In `release` builds, HMR is disabled.

## Learn more

- [rwf-admin](https://github.com/levkk/rwf/blob/main/rwf-admin/src/main.rs) uses HMR
37 changes: 37 additions & 0 deletions docs/docs/user-guides/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Local dev overview

Local development with Rwf benefits from the extensive Rust ecosystem, and makes some additions of its own, like [hot reloading](hot-reload.md) of frontend code.

To make your development experience smoother, we recommend you install `cargo-watch` and `cargo-nextest`, like so:

```bash
cargo install cargo-watch cargo-nextest
```

## Watch for changes

`cargo-watch` can monitor your code for changes and restart the server automatically. This makes local development much easier: as you make edits to your code, you don't have to stop and start the server manually:

```bash
cargo watch --exec run
```

### Hot reload

Rwf can refresh pages automatically as they are being changed. If you enable [hot reload](hot-reload.md), and also use `cargo-watch`, make sure to tell it to ignore template changes:

```bash
cargo watch --exec run --ignore *.html
```

## Run tests

Running tests with `cargo-nextest` is faster and more ergonomic as opposed to using built-in `cargo test`. If you end up writing tests for your app, you can run them all in parallel:

```
cargo nextest run
```

## Learn more

- [Hot reload](hot-reload.md)
4 changes: 4 additions & 0 deletions docs/docs/views/turbo/.pages
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
nav:
- 'index.md'
- 'streams.md'
- '...'
4 changes: 4 additions & 0 deletions docs/docs/views/turbo/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ Otherwise, you can always get Turbo from a CDN, like [Skypack](https://www.skypa
## Using Turbo

Once Turbo is loaded, all links and forms will use Turbo automatically. When visiting links or submitting forms, Turbo will intercept the request, send it on the browser's behalf, process the response and replace the contents of the page seamlessly.

## Learn more

- [Turbo Streams](streams.md)
56 changes: 56 additions & 0 deletions docs/docs/views/turbo/streams.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Turbo Streams

Turbo can process page changes received via a [WebSocket](../../controllers/websockets.md) connection. This enables the server to dynamically update the client's page without the client clicking links or submitting forms. Rwf supports this out of the box.

#### WebSocket endpoint

Since Turbo Streams use WebSockets to push changes to the client, you need to create a WebSocket endpoint first. Rwf comes with a controller to do just that without any additional configuration:

```rust
use rwf::prelude::*;
use rwf::http::Server;
use rwf::controllers::TurboStream;

#[tokio::main]
async fn main() {
Server::new(vec![
route!("/turbo-stream" => TurboStream),
])
.launch("0.0.0.0:8000")
.await
.unwrap()
}
```

#### Connect the app

Turbo has a special HTML element which automatically handles WebSocket connections, called `<turbo-stream-source>`. This element needs to specify the WebSocket endpoint and be placed in the body of all pages that wish to support Turbo Streams, for example:

```html
<html>
<body>
<turbo-stream-source
src="ws://localhost:8000/turbo-stream">
</turbo-stream-source>
<!-- ... -->
```

WebSocket connections need to specify the absolute URL for the WebSocket server. The endpoint above uses the development server you're running on localhost, but in production this will be different. To make this easier, Rwf comes with a handy template function which figures out which endpoint to use based on your website's URL:

```erb
<html>
<body>
<%- rwf_turbo_stream("/turbo-stream") %>
<!-- ... -->
```

If your website is running on `https://example.com`, this function will create a Turbo Stream connection pointing to `wss://example.com/turbo-stream`.

!!! note
The `<turbo-stream-source>` element must be placed inside the `<body>`. When visiting pages, Turbo updates the `<body>` element only, while keeping other elements like `<head>` intact. To make sure
Turbo reconnects to your stream endpoint when loading a page, the stream element needs to be recreated on each page visit.

## Learn more

- [WebSockets](../../controllers/websockets.md)
- [Template functions](../templates/functions.md)
2 changes: 1 addition & 1 deletion rwf-admin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ async fn main() -> Result<(), http::Error> {
Logger::init();
Migrations::migrate().await?;

#[cfg(debug_assertions)]
// Enable HMR.
rwf::hmr::hmr(PathBuf::from("templates"));

// Basic auth is just an example, it's not secure. I would recommend using SessionAuth
Expand Down
4 changes: 4 additions & 0 deletions rwf/src/hmr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use tokio::time::sleep;
use crate::http::websocket::Message;
use crate::{comms::Comms, view::TurboStream};

#[cfg(debug_assertions)]
pub fn hmr(path: PathBuf) {
tokio::task::spawn(async move {
let mut watcher = notify::recommended_watcher(|res: Result<Event>| match res {
Expand All @@ -33,3 +34,6 @@ pub fn hmr(path: PathBuf) {
Result::Ok(())
});
}

#[cfg(not(debug_assertions))]
pub fn hmr(_path: PathBuf) {}
1 change: 0 additions & 1 deletion rwf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ pub mod config;
pub mod controller;
pub mod crypto;
pub mod error;
#[cfg(debug_assertions)]
pub mod hmr;
pub mod http;
pub mod job;
Expand Down

0 comments on commit 18f07b0

Please sign in to comment.