Pārlūkot izejas kodu

parse and inject pre rendered content to work with trunk

Evan Almloff 2 gadi atpakaļ
vecāks
revīzija
9877dd7ed8

+ 38 - 10
packages/server/src/adapters/axum_adapter.rs

@@ -70,23 +70,51 @@ where
     }
 
     fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
-        self,
+        mut self,
         server_fn_route: &'static str,
         cfg: impl Into<ServeConfig<P>>,
     ) -> Self {
-        use tower_http::services::ServeDir;
+        use tower_http::services::{ServeDir, ServeFile};
 
         let cfg = cfg.into();
 
-        // Serve the dist folder and the index.html file
-        let serve_dir = ServeDir::new(cfg.assets_path);
-
-        self.register_server_fns(server_fn_route)
-            .nest_service("/assets", serve_dir)
-            .route_service(
-                "/",
-                get(render_handler).with_state((cfg, SSRState::default())),
+        // Serve all files in dist folder except index.html
+        let dir = std::fs::read_dir(cfg.assets_path).unwrap_or_else(|e| {
+            panic!(
+                "Couldn't read assets directory at {:?}: {}",
+                &cfg.assets_path, e
             )
+        });
+
+        for entry in dir.flatten() {
+            let path = entry.path();
+            if path.ends_with("index.html") {
+                continue;
+            }
+            let route = path
+                .strip_prefix(&cfg.assets_path)
+                .unwrap()
+                .iter()
+                .map(|segment| {
+                    segment.to_str().unwrap_or_else(|| {
+                        panic!("Failed to convert path segment {:?} to string", segment)
+                    })
+                })
+                .collect::<Vec<_>>()
+                .join("/");
+            let route = format!("/{}", route);
+            if path.is_dir() {
+                self = self.nest_service(&route, ServeDir::new(path));
+            } else {
+                self = self.nest_service(&route, ServeFile::new(path));
+            }
+        }
+
+        // Add server functions and render index.html
+        self.register_server_fns(server_fn_route).route(
+            "/",
+            get(render_handler).with_state((cfg, SSRState::default())),
+        )
     }
 }
 

+ 31 - 5
packages/server/src/adapters/salvo_adapter.rs

@@ -50,24 +50,50 @@ impl DioxusRouterExt for Router {
     }
 
     fn register_server_fns(self, server_fn_route: &'static str) -> Self {
-        self.register_server_fns_with_handler(|| ServerFnHandler {
+        self.register_server_fns_with_handler(server_fn_route, |func| ServerFnHandler {
             server_context: DioxusServerContext::default(),
             function: func,
         })
     }
 
     fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
-        self,
+        mut self,
         server_fn_route: &'static str,
         cfg: impl Into<ServeConfig<P>>,
     ) -> Self {
         let cfg = cfg.into();
-        // Serve the dist folder and the index.html file
-        let serve_dir = StaticDir::new([cfg.assets_path]);
+
+        // Serve all files in dist folder except index.html
+        let dir = std::fs::read_dir(cfg.assets_path).unwrap_or_else(|e| {
+            panic!(
+                "Couldn't read assets directory at {:?}: {}",
+                &cfg.assets_path, e
+            )
+        });
+
+        for entry in dir.flatten() {
+            let path = entry.path();
+            if path.ends_with("index.html") {
+                continue;
+            }
+            let serve_dir = StaticDir::new([path.clone()]);
+            let route = path
+                .strip_prefix(&cfg.assets_path)
+                .unwrap()
+                .iter()
+                .map(|segment| {
+                    segment.to_str().unwrap_or_else(|| {
+                        panic!("Failed to convert path segment {:?} to string", segment)
+                    })
+                })
+                .collect::<Vec<_>>()
+                .join("/");
+            let route = format!("/{}/<**path>", route);
+            self = self.push(Router::with_path(route).get(serve_dir))
+        }
 
         self.register_server_fns(server_fn_route)
             .push(Router::with_path("/").get(SSRHandler { cfg }))
-            .push(Router::with_path("assets/<**path>").get(serve_dir))
     }
 }
 

+ 1 - 1
packages/server/src/adapters/warp_adapter.rs

@@ -69,7 +69,7 @@ pub fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
             .and(warp::get())
             .and(with_ssr_state())
             .map(move |renderer: SSRState| warp::reply::html(renderer.render(&cfg))))
-        .or(warp::path("assets").and(serve_dir))
+        .or(serve_dir)
         .boxed()
 }
 

+ 3 - 36
packages/server/src/render.rs

@@ -1,4 +1,3 @@
-use std::fmt::Write;
 use std::sync::Arc;
 
 use dioxus_core::VirtualDom;
@@ -47,12 +46,7 @@ impl Default for SSRState {
 impl SSRState {
     pub fn render<P: 'static + Clone>(&self, cfg: &ServeConfig<P>) -> String {
         let ServeConfig {
-            app,
-            application_name,
-            base_path,
-            head,
-            props,
-            ..
+            app, props, index, ..
         } = cfg;
 
         let mut vdom = VirtualDom::new_with_props(*app, props.clone());
@@ -63,38 +57,11 @@ impl SSRState {
 
         let mut html = String::new();
 
-        let result = write!(
-            &mut html,
-            r#"
-        <!DOCTYPE html>
-        <html>
-        <head>{head}
-        </head><body>
-        <div id="main">"#
-        );
-
-        if let Err(err) = result {
-            eprintln!("Failed to write to html: {}", err);
-        }
+        html += &index.pre_main;
 
         let _ = renderer.render_to(&mut html, &vdom);
 
-        if let Err(err) = write!(
-            &mut html,
-            r#"</div>
-        <script type="module">
-        import init from "/{base_path}/assets/dioxus/{application_name}.js";
-    init("/{base_path}/assets/dioxus/{application_name}_bg.wasm").then(wasm => {{
-      if (wasm.__wbindgen_start == undefined) {{
-          wasm.main();
-        }}
-    }});
-    </script>
-    </body>
-    </html>"#
-        ) {
-            eprintln!("Failed to write to html: {}", err);
-        }
+        html += &index.post_main;
 
         html
     }

+ 59 - 33
packages/server/src/serve.rs

@@ -1,12 +1,15 @@
+use std::fs::File;
+use std::io::Read;
+use std::path::PathBuf;
+
 use dioxus_core::Component;
 
 #[derive(Clone)]
 pub struct ServeConfigBuilder<P: Clone> {
     pub(crate) app: Component<P>,
     pub(crate) props: P,
-    pub(crate) application_name: Option<&'static str>,
-    pub(crate) base_path: Option<&'static str>,
-    pub(crate) head: Option<&'static str>,
+    pub(crate) root_id: Option<&'static str>,
+    pub(crate) index_path: Option<&'static str>,
     pub(crate) assets_path: Option<&'static str>,
 }
 
@@ -16,32 +19,25 @@ impl<P: Clone> ServeConfigBuilder<P> {
         Self {
             app,
             props,
-            application_name: None,
-            base_path: None,
-            head: None,
+            root_id: None,
+            index_path: None,
             assets_path: None,
         }
     }
 
-    /// Set the application name matching the name in the Dioxus.toml file used to build the application
-    pub fn application_name(mut self, application_name: &'static str) -> Self {
-        self.application_name = Some(application_name);
+    /// Set the path of the index.html file to be served. (defaults to {assets_path}/index.html)
+    pub fn index_path(mut self, index_path: &'static str) -> Self {
+        self.index_path = Some(index_path);
         self
     }
 
-    /// Set the path the WASM application will be served under
-    pub fn base_path(mut self, base_path: &'static str) -> Self {
-        self.base_path = Some(base_path);
+    /// Set the id of the root element in the index.html file to place the prerendered content into. (defaults to main)
+    pub fn root_id(mut self, root_id: &'static str) -> Self {
+        self.root_id = Some(root_id);
         self
     }
 
-    /// Set the head content to be included in the HTML document served
-    pub fn head(mut self, head: &'static str) -> Self {
-        self.head = Some(head);
-        self
-    }
-
-    /// Set the path of the assets folder generated by the Dioxus CLI. (defaults to dist/assets)
+    /// Set the path of the assets folder generated by the Dioxus CLI. (defaults to dist)
     pub fn assets_path(mut self, assets_path: &'static str) -> Self {
         self.assets_path = Some(assets_path);
         self
@@ -49,31 +45,61 @@ impl<P: Clone> ServeConfigBuilder<P> {
 
     /// Build the ServeConfig
     pub fn build(self) -> ServeConfig<P> {
-        let base_path = self.base_path.unwrap_or(".");
-        let application_name = self.application_name.unwrap_or("dioxus");
+        let assets_path = self.assets_path.unwrap_or("dist");
+
+        let index_path = self
+            .index_path
+            .map(PathBuf::from)
+            .unwrap_or_else(|| format!("{assets_path}/index.html").into());
+
+        let root_id = self.root_id.unwrap_or("main");
+
+        let index = load_index_html(index_path, root_id);
+
         ServeConfig {
             app: self.app,
             props: self.props,
-            application_name,
-            base_path,
-            head: self.head.map(String::from).unwrap_or(format!(r#"<title>Dioxus Application</title>
-        <link rel="preload" href="/{base_path}/assets/dioxus/{application_name}_bg.wasm" as="fetch" type="application/wasm" crossorigin="" />
-        <link rel="modulepreload" href="/{base_path}/assets/dioxus/{application_name}.js" />
-        <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
-        <meta name="viewport" content="width=device-width, initial-scale=1" />
-        <meta charset="UTF-8" />"#)),
-            assets_path: self.assets_path.unwrap_or("dist/assets"),
+            index,
+            assets_path,
         }
     }
 }
 
+fn load_index_html(path: PathBuf, root_id: &'static str) -> IndexHtml {
+    let mut file = File::open(path).expect("Failed to find index.html. Make sure the index_path is set correctly and the WASM application has been built.");
+
+    let mut contents = String::new();
+    file.read_to_string(&mut contents)
+        .expect("Failed to read index.html");
+
+    let (pre_main, post_main) = contents.split_once(&format!("id=\"{root_id}\"")).unwrap_or_else(|| panic!("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('>').unwrap_or_else(|| {
+        panic!("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(),
+    );
+
+    IndexHtml {
+        pre_main,
+        post_main,
+    }
+}
+
+#[derive(Clone)]
+pub(crate) struct IndexHtml {
+    pub(crate) pre_main: String,
+    pub(crate) post_main: String,
+}
+
 #[derive(Clone)]
 pub struct ServeConfig<P: Clone> {
     pub(crate) app: Component<P>,
     pub(crate) props: P,
-    pub(crate) application_name: &'static str,
-    pub(crate) base_path: &'static str,
-    pub(crate) head: String,
+    pub(crate) index: IndexHtml,
     pub(crate) assets_path: &'static str,
 }