use anyhow::Context; use std::path::Path; /// An `IndexHtml` represents the contents of an `index.html` file used to serve a web application. /// /// This defines the static portion of your web application, typically generated by a tool like `dx` /// in conjunction with wasm-bindgen. /// /// This structure expects a well-formed HTML document split into several sections: /// - `head_before_title`: The portion of the `` section before the `` tag and its contents. /// - `head_after_title`: The portion of the `<head>` section after the `<title>` tag. /// - `close_head`: The closing `</head>` tag and any content following it. /// - `post_main`: The content following the main application container (e.g., `<div id="main">`). /// - `after_closing_body_tag`: The content following the closing `</body>` tag. /// /// These fields are not explicitly exposed as part of the API, but are critical for dioxus-fullstack /// to properly inject server-rendered content and client-side bootstrapping scripts into the HTML. /// /// The simplest HTML document that satisfies this structure is: /// /// ```html /// <html> /// <head> </head> /// <body> /// <div id="main"></div> /// </body> /// </html> /// ``` #[derive(Clone, Debug)] pub struct IndexHtml { pub(crate) head_before_title: String, pub(crate) head_after_title: String, pub(crate) title: String, pub(crate) close_head: String, pub(crate) post_main: String, pub(crate) after_closing_body_tag: String, } impl IndexHtml { /// Create a new `IndexHtml` from the raw contents of an `index.html` file. /// /// This function will parse the `index.html` and split it into sections for easier manipulation. /// The `root_id` parameter specifies the id of the main application container (e.g., "main") /// which your app will render into. pub fn new(contents: &str, root_id: &str) -> Result<IndexHtml, anyhow::Error> { let (pre_main, post_main) = contents.split_once(&format!("id=\"{root_id}\"")) .with_context(|| format!("Failed to find id=\"{root_id}\" in index.html. The id is used to inject the application into the page."))?; let post_main = post_main.split_once('>').with_context(|| { format!("Failed to find closing > after id=\"{root_id}\" in index.html.") })?; let (pre_main, post_main) = ( pre_main.to_string() + &format!("id=\"{root_id}\"") + post_main.0 + ">", post_main.1.to_string(), ); let (head, close_head) = pre_main.split_once("</head>").with_context(|| { format!("Failed to find closing </head> tag after id=\"{root_id}\" in index.html.") })?; let (head, close_head) = (head.to_string(), "</head>".to_string() + close_head); let (post_main, after_closing_body_tag) = post_main.split_once("</body>").with_context(|| { format!("Failed to find closing </body> tag after id=\"{root_id}\" in index.html.") })?; // Strip out the head if it exists let mut head_before_title = String::new(); let mut head_after_title = head; let mut title = String::new(); if let Some((new_head_before_title, new_title)) = head_after_title.split_once("<title>") { let (new_title, new_head_after_title) = new_title .split_once("") .context("Failed to find closing tag after in index.html.")?; title = format!("<title>{new_title}"); head_before_title = new_head_before_title.to_string(); head_after_title = new_head_after_title.to_string(); } Ok(IndexHtml { head_before_title, head_after_title, title, close_head, post_main: post_main.to_string(), after_closing_body_tag: "".to_string() + after_closing_body_tag, }) } /// Load an `IndexHtml` from a file at the given path. /// /// This function reads the contents of the file and parses it into an `IndexHtml`. /// The `id` parameter specifies the id of the main application container (e.g., "main"). pub fn from_file(path: &Path, id: &str) -> Result { let contents = std::fs::read_to_string(path) .with_context(|| format!("Failed to read index.html from {}", path.display()))?; IndexHtml::new(&contents, id) } /// Create a default `IndexHtml` suitable for server-side rendering (SSR) only applications. /// This will not include any customizations to the HTML nor any JavaScript to bootstrap a client-side app. pub fn ssr_only() -> Self { const DEFAULT: &str = r#"
"#; Self::new(DEFAULT, "main").expect("Failed to load default index.html") } }