feat: pass config from server to client (#407)

This commit is contained in:
Valerio Ageno
2025-01-23 18:43:17 +01:00
committed by GitHub
parent 8fe8930def
commit 305ae59086
10 changed files with 102 additions and 40 deletions
+3 -3
View File
@@ -1,15 +1,15 @@
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use std::fs::read_to_string;
use std::io;
use std::path::PathBuf;
#[derive(Deserialize, Debug, Clone)]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
}
#[derive(Deserialize, Debug, Clone)]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Config {
pub server: ServerConfig,
}
+1
View File
@@ -33,6 +33,7 @@ tower-http = {version = "0.6.0", features = ["fs"]}
colored = "2.1.0"
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.17.3"}
tuono_internal = {path = "../tuono_internal", version = "0.17.3"}
# Match the same version used by axum
tokio-tungstenite = "0.24.0"
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
+4
View File
@@ -0,0 +1,4 @@
use once_cell::sync::OnceCell;
use tuono_internal::config::Config;
pub static GLOBAL_CONFIG: OnceCell<Config> = OnceCell::new();
+1
View File
@@ -4,6 +4,7 @@
//! You can find the full documentation at [tuono.dev](https://tuono.dev/)
mod catch_all;
mod config;
mod logger;
mod manifest;
mod mode;
+19 -1
View File
@@ -1,8 +1,10 @@
use crate::config::GLOBAL_CONFIG;
use crate::manifest::MANIFEST;
use crate::mode::{Mode, GLOBAL_MODE};
use erased_serde::Serialize;
use regex::Regex;
use serde::Serialize as SerdeSerialize;
use tuono_internal::config::ServerConfig;
use crate::request::{Location, Request};
@@ -21,16 +23,31 @@ pub struct Payload<'a> {
js_bundles: Option<Vec<&'a String>>,
#[serde(rename(serialize = "cssBundles"))]
css_bundles: Option<Vec<&'a String>>,
#[serde(rename(serialize = "devServerConfig"))]
dev_server_config: Option<&'a ServerConfig>,
}
impl<'a> Payload<'a> {
pub fn new(req: &'a Request, props: &'a dyn Serialize) -> Payload<'a> {
let config = GLOBAL_CONFIG
.get()
.expect("Failed to load the current config");
let mode = *GLOBAL_MODE.get().expect("Failed to load the current mode");
let dev_server_config = if mode == Mode::Dev {
Some(&config.server)
} else {
None
};
Payload {
router: req.location(),
props,
mode: *GLOBAL_MODE.get().expect("Failed to load the current mode"),
mode,
js_bundles: None,
css_bundles: None,
dev_server_config,
}
}
@@ -221,6 +238,7 @@ mod tests {
mode,
js_bundles: None,
css_bundles: None,
dev_server_config: None,
}
}
+17 -6
View File
@@ -1,9 +1,11 @@
use crate::config::GLOBAL_CONFIG;
use crate::manifest::load_manifest;
use crate::mode::{Mode, GLOBAL_MODE};
use axum::routing::{get, Router};
use colored::Colorize;
use ssr_rs::Ssr;
use tower_http::services::ServeDir;
use tuono_internal::config::Config;
use crate::{
catch_all::catch_all, logger::LoggerLayer, vite_reverse_proxy::vite_reverse_proxy,
@@ -23,6 +25,9 @@ impl Server {
Ssr::create_platform();
GLOBAL_MODE.set(mode).unwrap();
GLOBAL_CONFIG
.set(Config::get().expect("[SERVER] Failed to load config"))
.unwrap();
if mode == Mode::Prod {
load_manifest()
@@ -32,10 +37,19 @@ impl Server {
}
pub async fn start(&self) {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
let config = GLOBAL_CONFIG
.get()
.expect("Failed to get the internal config");
let server_http_address = format!("{}:{}", config.server.host, config.server.port);
let listener = tokio::net::TcpListener::bind(&server_http_address)
.await
.unwrap();
let server_url = format!("http://{}", &server_http_address);
if self.mode == Mode::Dev {
println!(" Ready at: {}\n", "http://localhost:3000".blue().bold());
println!(" Ready at: {}\n", &server_url.blue().bold());
let router = self
.router
.to_owned()
@@ -51,10 +65,7 @@ impl Server {
.await
.expect("Failed to serve development server");
} else {
println!(
" Production server at: {}\n",
"http://localhost:3000".blue().bold()
);
println!(" Production server at: {}\n", &server_url.blue().bold());
let router = self
.router
.to_owned()
+12 -3
View File
@@ -1,3 +1,4 @@
use crate::config::GLOBAL_CONFIG;
use axum::body::Body;
use axum::extract::Path;
@@ -5,12 +6,20 @@ use axum::http::{HeaderName, HeaderValue};
use axum::response::{IntoResponse, Response};
use reqwest::Client;
const VITE_URL: &str = "http://localhost:3001/vite-server";
pub async fn vite_reverse_proxy(Path(path): Path<String>) -> impl IntoResponse {
let client = Client::new();
match client.get(format!("{VITE_URL}/{path}")).send().await {
let config = GLOBAL_CONFIG
.get()
.expect("Failed to get the internal config");
let vite_url = format!(
"http://{}:{}/vite-server",
config.server.host,
config.server.port + 1
);
match client.get(format!("{vite_url}/{path}")).send().await {
Ok(res) => {
let mut response_builder = Response::builder().status(res.status().as_u16());
+12 -2
View File
@@ -1,3 +1,4 @@
use crate::config::GLOBAL_CONFIG;
use axum::extract::ws::{self, WebSocket, WebSocketUpgrade};
use axum::response::IntoResponse;
use futures_util::{SinkExt, StreamExt};
@@ -6,7 +7,6 @@ use tokio_tungstenite::tungstenite::{Error, Message};
use tungstenite::client::IntoClientRequest;
use tungstenite::ClientRequestBuilder;
const VITE_WS: &str = "ws://localhost:3001/vite-server/";
const VITE_WS_PROTOCOL: &str = "vite-hmr";
/// This is the entry point to proxy the vite's WebSocket.
@@ -28,7 +28,17 @@ async fn handle_socket(mut tuono_socket: WebSocket) {
return;
}
let vite_ws_request = ClientRequestBuilder::new(VITE_WS.parse().unwrap())
let config = GLOBAL_CONFIG
.get()
.expect("Failed to get the internal config");
let vite_ws = format!(
"ws://{}:{}/vite-server/",
config.server.host,
config.server.port + 1
);
let vite_ws_request = ClientRequestBuilder::new(vite_ws.parse().unwrap())
.with_sub_protocol(VITE_WS_PROTOCOL)
.into_client_request()
.expect("Failed to create vite WS request");
+4
View File
@@ -17,6 +17,10 @@ export interface ServerProps<TProps = unknown> {
jsBundles: Array<string>
cssBundles: Array<string>
mode: 'Dev' | 'Prod'
devServerConfig: {
port: number
host: string
}
}
export interface RouteProps<TData = unknown> {
+29 -25
View File
@@ -1,29 +1,33 @@
import type { JSX } from 'react'
import { useRouterContext } from 'tuono-router'
const TUONO_DEV_SERVER_PORT = 3000
const VITE_PROXY_PATH = '/vite-server'
const SCRIPT_BASE_URL = `http://localhost:${TUONO_DEV_SERVER_PORT}${VITE_PROXY_PATH}`
const DEFAULT_SERVER_CONFIG = { host: 'localhost', port: 3000 }
export const DevResources = (): JSX.Element => (
<>
<script type="module" async>
{[
`import RefreshRuntime from '${SCRIPT_BASE_URL}/@react-refresh'`,
'RefreshRuntime.injectIntoGlobalHook(window)',
'window.$RefreshReg$ = () => {}',
'window.$RefreshSig$ = () => (type) => type',
'window.__vite_plugin_react_preamble_installed__ = true',
].join('\n')}
</script>
<script
type="module"
async
src={`${SCRIPT_BASE_URL}/@vite/client`}
></script>
<script
type="module"
async
src={`${SCRIPT_BASE_URL}/client-main.tsx`}
></script>
</>
)
export const DevResources = (): JSX.Element => {
const { serverSideProps } = useRouterContext()
const { host, port } =
serverSideProps?.devServerConfig ?? DEFAULT_SERVER_CONFIG
const viteBaseUrl = `http://${host}:${port}${VITE_PROXY_PATH}`
return (
<>
<script type="module" async>
{[
`import RefreshRuntime from '${viteBaseUrl}/@react-refresh'`,
'RefreshRuntime.injectIntoGlobalHook(window)',
'window.$RefreshReg$ = () => {}',
'window.$RefreshSig$ = () => (type) => type',
'window.__vite_plugin_react_preamble_installed__ = true',
].join('\n')}
</script>
<script type="module" async src={`${viteBaseUrl}/@vite/client`}></script>
<script
type="module"
async
src={`${viteBaseUrl}/client-main.tsx`}
></script>
</>
)
}