1#![allow(clippy::needless_lifetimes)]
2
3use crate::{prelude::*, WasmSplitManifest};
4use leptos_config::LeptosOptions;
5use leptos_macro::{component, view};
6use std::{path::PathBuf, sync::OnceLock};
7
8#[component]
12pub fn AutoReload(
13 #[prop(optional)]
15 disable_watch: bool,
16 options: LeptosOptions,
18) -> impl IntoView {
19 (!disable_watch && std::env::var("LEPTOS_WATCH").is_ok()).then(|| {
20 #[cfg(feature = "nonce")]
21 let nonce = crate::nonce::use_nonce();
22 #[cfg(not(feature = "nonce"))]
23 let nonce = None::<()>;
24
25 let reload_port = match options.reload_external_port {
26 Some(val) => val,
27 None => options.reload_port,
28 };
29 let protocol = match options.reload_ws_protocol {
30 leptos_config::ReloadWSProtocol::WS => "'ws://'",
31 leptos_config::ReloadWSProtocol::WSS => "'wss://'",
32 };
33
34 let script = format!(
35 "(function (reload_port, protocol) {{ {} {} }})({reload_port:?}, \
36 {protocol})",
37 leptos_hot_reload::HOT_RELOAD_JS,
38 include_str!("reload_script.js")
39 );
40 view! { <script nonce=nonce>{script}</script> }
41 })
42}
43
44#[component]
48pub fn HydrationScripts(
49 options: LeptosOptions,
51 #[prop(optional)]
53 islands: bool,
54 #[prop(optional)]
57 islands_router: bool,
58 #[prop(optional, into)]
60 root: Option<String>,
61) -> impl IntoView {
62 static SPLIT_MANIFEST: OnceLock<Option<WasmSplitManifest>> =
63 OnceLock::new();
64
65 if let Some(splits) = SPLIT_MANIFEST.get_or_init(|| {
66 let root = root.clone().unwrap_or_default();
67
68 let (wasm_split_js, wasm_split_manifest) = if options.hash_files {
69 let hash_path = std::env::current_exe()
70 .map(|path| {
71 path.parent().map(|p| p.to_path_buf()).unwrap_or_default()
72 })
73 .unwrap_or_default()
74 .join(options.hash_file.as_ref());
75 let hashes = std::fs::read_to_string(&hash_path)
76 .expect("failed to read hash file");
77
78 let mut split =
79 "__wasm_split.______________________.js".to_string();
80 let mut manifest = "__wasm_split_manifest.json".to_string();
81 for line in hashes.lines() {
82 let line = line.trim();
83 if !line.is_empty() {
84 if let Some((file, hash)) = line.split_once(':') {
85 if file == "manifest" {
86 manifest.clear();
87 manifest.push_str("__wasm_split_manifest.");
88 manifest.push_str(hash.trim());
89 manifest.push_str(".json");
90 }
91 if file == "split" {
92 split.clear();
93 split.push_str("__wasm_split.");
94 split.push_str(hash.trim());
95 split.push_str(".js");
96 }
97 }
98 }
99 }
100 (split, manifest)
101 } else {
102 (
103 "__wasm_split.______________________.js".to_string(),
104 "__wasm_split_manifest.json".to_string(),
105 )
106 };
107
108 let site_dir = &options.site_root;
109 let pkg_dir = &options.site_pkg_dir;
110 let path = PathBuf::from(site_dir.to_string());
111 let path = path.join(pkg_dir.to_string()).join(wasm_split_manifest);
112 let file = std::fs::read_to_string(path).ok()?;
113
114 let manifest = WasmSplitManifest(ArcStoredValue::new((
115 format!("{root}/{pkg_dir}"),
116 serde_json::from_str(&file).expect("could not read manifest file"),
117 wasm_split_js,
118 )));
119
120 Some(manifest)
121 }) {
122 provide_context(splits.clone());
123 }
124
125 let mut js_file_name = options.output_name.to_string();
126 let mut wasm_file_name = options.output_name.to_string();
127 if options.hash_files {
128 let hash_path = std::env::current_exe()
129 .map(|path| {
130 path.parent().map(|p| p.to_path_buf()).unwrap_or_default()
131 })
132 .unwrap_or_default()
133 .join(options.hash_file.as_ref());
134 if hash_path.exists() {
135 let hashes = std::fs::read_to_string(&hash_path)
136 .expect("failed to read hash file");
137 for line in hashes.lines() {
138 let line = line.trim();
139 if !line.is_empty() {
140 if let Some((file, hash)) = line.split_once(':') {
141 if file == "js" {
142 js_file_name.push_str(&format!(".{}", hash.trim()));
143 } else if file == "wasm" {
144 wasm_file_name
145 .push_str(&format!(".{}", hash.trim()));
146 }
147 }
148 }
149 }
150 } else {
151 leptos::logging::error!(
152 "File hashing is active but no hash file was found"
153 );
154 }
155 } else if std::option_env!("LEPTOS_OUTPUT_NAME").is_none() {
156 wasm_file_name.push_str("_bg");
157 }
158
159 let pkg_path = &options.site_pkg_dir;
160 #[cfg(feature = "nonce")]
161 let nonce = crate::nonce::use_nonce();
162 #[cfg(not(feature = "nonce"))]
163 let nonce = None::<String>;
164 let script = if islands {
165 if let Some(sc) = Owner::current_shared_context() {
166 sc.set_is_hydrating(false);
167 }
168 include_str!("./island_script.js")
169 } else {
170 include_str!("./hydration_script.js")
171 };
172
173 let islands_router = islands_router
174 .then_some(include_str!("./islands_routing.js"))
175 .unwrap_or_default();
176
177 let root = root.unwrap_or_default();
178 view! {
179 <link rel="modulepreload" href=format!("{root}/{pkg_path}/{js_file_name}.js") crossorigin=nonce.clone()/>
180 <link
181 rel="preload"
182 href=format!("{root}/{pkg_path}/{wasm_file_name}.wasm")
183 r#as="fetch"
184 r#type="application/wasm"
185 crossorigin=nonce.clone().unwrap_or_default()
186 />
187 <script type="module" nonce=nonce>
188 {format!("{script}({root:?}, {pkg_path:?}, {js_file_name:?}, {wasm_file_name:?});{islands_router}")}
189 </script>
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
203pub struct IslandsRouterNavigation;