feat: add reload websocket configuration and enable env configuration (#1613)

This commit is contained in:
IcosaHedron 2023-09-01 20:51:46 -04:00 committed by GitHub
parent f5b4b97c9b
commit d9e83121c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 109 additions and 23 deletions

View file

@ -11,14 +11,17 @@ fn autoreload(nonce_str: &str, options: &LeptosOptions) -> String {
Some(val) => val,
None => options.reload_port,
};
let protocol = match options.reload_ws_protocol {
leptos_config::ReloadWSProtocol::WS => "'ws://'",
leptos_config::ReloadWSProtocol::WSS => "'wss://'",
};
match std::env::var("LEPTOS_WATCH").is_ok() {
true => format!(
r#"
<script crossorigin=""{nonce_str}>(function () {{
{}
let protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
let host = window.location.hostname;
let ws = new WebSocket(protocol + host + ':{reload_port}/live_reload');
let ws = new WebSocket({protocol} + host + ':{reload_port}/live_reload');
ws.onmessage = (ev) => {{
let msg = JSON.parse(ev.data);
if (msg.all) window.location.reload();

View file

@ -61,6 +61,11 @@ pub struct LeptosOptions {
#[builder(default)]
#[serde(default)]
pub reload_external_port: Option<u32>,
/// The protocol the Websocket watcher uses on the client: `ws` in most cases, `wss` when behind a reverse https proxy.
/// Defaults to `ws`
#[builder(default)]
#[serde(default)]
pub reload_ws_protocol: ReloadWSProtocol,
}
impl LeptosOptions {
@ -84,7 +89,7 @@ impl LeptosOptions {
output_name,
site_root: env_w_default("LEPTOS_SITE_ROOT", "target/site")?,
site_pkg_dir: env_w_default("LEPTOS_SITE_PKG_DIR", "pkg")?,
env: Env::default(),
env: env_from_str(env_w_default("LEPTOS_ENV", "DEV")?.as_str())?,
site_addr: env_w_default("LEPTOS_SITE_ADDR", "127.0.0.1:3000")?
.parse()?,
reload_port: env_w_default("LEPTOS_RELOAD_PORT", "3001")?
@ -95,6 +100,9 @@ impl LeptosOptions {
Some(val) => Some(val.parse()?),
None => None,
},
reload_ws_protocol: ws_from_str(
env_w_default("LEPTOS_RELOAD_WS_PROTOCOL", "ws")?.as_str(),
)?,
})
}
}
@ -151,45 +159,103 @@ impl Default for Env {
}
}
fn from_str(input: &str) -> Result<Env, String> {
fn env_from_str(input: &str) -> Result<Env, LeptosConfigError> {
let sanitized = input.to_lowercase();
match sanitized.as_ref() {
"dev" | "development" => Ok(Env::DEV),
"prod" | "production" => Ok(Env::PROD),
_ => Err(format!(
_ => Err(LeptosConfigError::EnvVarError(format!(
"{input} is not a supported environment. Use either `dev` or \
`production`.",
)),
))),
}
}
impl FromStr for Env {
type Err = ();
fn from_str(input: &str) -> Result<Self, Self::Err> {
from_str(input).or_else(|_| Ok(Self::default()))
env_from_str(input).or_else(|_| Ok(Self::default()))
}
}
impl From<&str> for Env {
fn from(str: &str) -> Self {
from_str(str).unwrap_or_else(|err| panic!("{}", err))
env_from_str(str).unwrap_or_else(|err| panic!("{}", err))
}
}
impl From<&Result<String, VarError>> for Env {
fn from(input: &Result<String, VarError>) -> Self {
match input {
Ok(str) => from_str(str).unwrap_or_else(|err| panic!("{}", err)),
Ok(str) => {
env_from_str(str).unwrap_or_else(|err| panic!("{}", err))
}
Err(_) => Self::default(),
}
}
}
impl TryFrom<String> for Env {
type Error = String;
type Error = LeptosConfigError;
fn try_from(s: String) -> Result<Self, Self::Error> {
from_str(s.as_str())
env_from_str(s.as_str())
}
}
/// An enum that can be used to define the websocket protocol Leptos uses for hotreloading
/// Defaults to `ws`.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub enum ReloadWSProtocol {
WS,
WSS,
}
impl Default for ReloadWSProtocol {
fn default() -> Self {
Self::WS
}
}
fn ws_from_str(input: &str) -> Result<ReloadWSProtocol, LeptosConfigError> {
let sanitized = input.to_lowercase();
match sanitized.as_ref() {
"ws" | "WS" => Ok(ReloadWSProtocol::WS),
"wss" | "WSS" => Ok(ReloadWSProtocol::WSS),
_ => Err(LeptosConfigError::EnvVarError(format!(
"{input} is not a supported websocket protocol. Use only `ws` or \
`wss`.",
))),
}
}
impl FromStr for ReloadWSProtocol {
type Err = ();
fn from_str(input: &str) -> Result<Self, Self::Err> {
ws_from_str(input).or_else(|_| Ok(Self::default()))
}
}
impl From<&str> for ReloadWSProtocol {
fn from(str: &str) -> Self {
ws_from_str(str).unwrap_or_else(|err| panic!("{}", err))
}
}
impl From<&Result<String, VarError>> for ReloadWSProtocol {
fn from(input: &Result<String, VarError>) -> Self {
match input {
Ok(str) => ws_from_str(str).unwrap_or_else(|err| panic!("{}", err)),
Err(_) => Self::default(),
}
}
}
impl TryFrom<String> for ReloadWSProtocol {
type Error = LeptosConfigError;
fn try_from(s: String) -> Result<Self, Self::Error> {
ws_from_str(s.as_str())
}
}

View file

@ -1,18 +1,31 @@
use crate::{env_w_default, env_wo_default, from_str, Env, LeptosOptions};
use crate::{
env_from_str, env_w_default, env_wo_default, ws_from_str, Env,
LeptosOptions, ReloadWSProtocol,
};
use std::{net::SocketAddr, str::FromStr};
#[test]
fn from_str_env() {
assert!(matches!(from_str("dev").unwrap(), Env::DEV));
assert!(matches!(from_str("development").unwrap(), Env::DEV));
assert!(matches!(from_str("DEV").unwrap(), Env::DEV));
assert!(matches!(from_str("DEVELOPMENT").unwrap(), Env::DEV));
assert!(matches!(from_str("prod").unwrap(), Env::PROD));
assert!(matches!(from_str("production").unwrap(), Env::PROD));
assert!(matches!(from_str("PROD").unwrap(), Env::PROD));
assert!(matches!(from_str("PRODUCTION").unwrap(), Env::PROD));
assert!(from_str("TEST").is_err());
assert!(from_str("?").is_err());
fn env_from_str_test() {
assert!(matches!(env_from_str("dev").unwrap(), Env::DEV));
assert!(matches!(env_from_str("development").unwrap(), Env::DEV));
assert!(matches!(env_from_str("DEV").unwrap(), Env::DEV));
assert!(matches!(env_from_str("DEVELOPMENT").unwrap(), Env::DEV));
assert!(matches!(env_from_str("prod").unwrap(), Env::PROD));
assert!(matches!(env_from_str("production").unwrap(), Env::PROD));
assert!(matches!(env_from_str("PROD").unwrap(), Env::PROD));
assert!(matches!(env_from_str("PRODUCTION").unwrap(), Env::PROD));
assert!(env_from_str("TEST").is_err());
assert!(env_from_str("?").is_err());
}
#[test]
fn ws_from_str_test() {
assert!(matches!(ws_from_str("ws").unwrap(), ReloadWSProtocol::WS));
assert!(matches!(ws_from_str("WS").unwrap(), ReloadWSProtocol::WS));
assert!(matches!(ws_from_str("wss").unwrap(), ReloadWSProtocol::WSS));
assert!(matches!(ws_from_str("WSS").unwrap(), ReloadWSProtocol::WSS));
assert!(ws_from_str("TEST").is_err());
assert!(ws_from_str("?").is_err());
}
#[test]
@ -49,6 +62,8 @@ fn try_from_env_test() {
std::env::set_var("LEPTOS_SITE_ADDR", "0.0.0.0:80");
std::env::set_var("LEPTOS_RELOAD_PORT", "8080");
std::env::set_var("LEPTOS_RELOAD_EXTERNAL_PORT", "8080");
std::env::set_var("LEPTOS_ENV", "PROD");
std::env::set_var("LEPTOS_RELOAD_WS_PROTOCOL", "WSS");
let config = LeptosOptions::try_from_env().unwrap();
assert_eq!(config.output_name, "app_test");
@ -61,4 +76,6 @@ fn try_from_env_test() {
);
assert_eq!(config.reload_port, 8080);
assert_eq!(config.reload_external_port, Some(8080));
assert_eq!(config.env, Env::PROD);
assert_eq!(config.reload_ws_protocol, ReloadWSProtocol::WSS)
}