leptos/hydration/
mod.rs

1#![allow(clippy::needless_lifetimes)]
2
3use crate::prelude::*;
4use leptos_config::LeptosOptions;
5use leptos_macro::{component, view};
6
7/// Inserts auto-reloading code used in `cargo-leptos`.
8///
9/// This should be included in the `<head>` of your application shell during development.
10#[component]
11pub fn AutoReload(
12    /// Whether the file-watching feature should be disabled.
13    #[prop(optional)]
14    disable_watch: bool,
15    /// Configuration options for this project.
16    options: LeptosOptions,
17) -> impl IntoView {
18    (!disable_watch && std::env::var("LEPTOS_WATCH").is_ok()).then(|| {
19        #[cfg(feature = "nonce")]
20        let nonce = crate::nonce::use_nonce();
21        #[cfg(not(feature = "nonce"))]
22        let nonce = None::<()>;
23
24        let reload_port = match options.reload_external_port {
25            Some(val) => val,
26            None => options.reload_port,
27        };
28        let protocol = match options.reload_ws_protocol {
29            leptos_config::ReloadWSProtocol::WS => "'ws://'",
30            leptos_config::ReloadWSProtocol::WSS => "'wss://'",
31        };
32
33        let script = format!(
34            "(function (reload_port, protocol) {{ {} {} }})({reload_port:?}, \
35             {protocol})",
36            leptos_hot_reload::HOT_RELOAD_JS,
37            include_str!("reload_script.js")
38        );
39        view! { <script nonce=nonce>{script}</script> }
40    })
41}
42
43/// Inserts hydration scripts that add interactivity to your server-rendered HTML.
44///
45/// This should be included in the `<head>` of your application shell.
46#[component]
47pub fn HydrationScripts(
48    /// Configuration options for this project.
49    options: LeptosOptions,
50    /// Should be `true` to hydrate in `islands` mode.
51    #[prop(optional)]
52    islands: bool,
53    /// A base url, not including a trailing slash
54    #[prop(optional, into)]
55    root: Option<String>,
56) -> impl IntoView {
57    let mut js_file_name = options.output_name.to_string();
58    let mut wasm_file_name = options.output_name.to_string();
59    if options.hash_files {
60        let hash_path = std::env::current_exe()
61            .map(|path| {
62                path.parent().map(|p| p.to_path_buf()).unwrap_or_default()
63            })
64            .unwrap_or_default()
65            .join(options.hash_file.as_ref());
66        if hash_path.exists() {
67            let hashes = std::fs::read_to_string(&hash_path)
68                .expect("failed to read hash file");
69            for line in hashes.lines() {
70                let line = line.trim();
71                if !line.is_empty() {
72                    if let Some((file, hash)) = line.split_once(':') {
73                        if file == "js" {
74                            js_file_name.push_str(&format!(".{}", hash.trim()));
75                        } else if file == "wasm" {
76                            wasm_file_name
77                                .push_str(&format!(".{}", hash.trim()));
78                        }
79                    }
80                }
81            }
82        }
83    } else if std::option_env!("LEPTOS_OUTPUT_NAME").is_none() {
84        wasm_file_name.push_str("_bg");
85    }
86
87    let pkg_path = &options.site_pkg_dir;
88    #[cfg(feature = "nonce")]
89    let nonce = crate::nonce::use_nonce();
90    #[cfg(not(feature = "nonce"))]
91    let nonce = None::<String>;
92    let script = if islands {
93        if let Some(sc) = Owner::current_shared_context() {
94            sc.set_is_hydrating(false);
95        }
96        include_str!("./island_script.js")
97    } else {
98        include_str!("./hydration_script.js")
99    };
100
101    let root = root.unwrap_or_default();
102    view! {
103        <link rel="modulepreload" href=format!("{root}/{pkg_path}/{js_file_name}.js") nonce=nonce.clone()/>
104        <link
105            rel="preload"
106            href=format!("{root}/{pkg_path}/{wasm_file_name}.wasm")
107            r#as="fetch"
108            r#type="application/wasm"
109            crossorigin=nonce.clone().unwrap_or_default()
110        />
111        <script type="module" nonce=nonce>
112            {format!("{script}({root:?}, {pkg_path:?}, {js_file_name:?}, {wasm_file_name:?})")}
113        </script>
114    }
115}