actix_web_nextjs/
spa.rs

1use std::borrow::Cow;
2use std::sync::Arc;
3
4use actix_files::Files;
5use actix_web::dev::HttpServiceFactory;
6use path_tree::PathTree;
7use tracing::warn;
8
9use crate::spa_service::SpaService;
10use crate::utils::{find_and_parse_build_manifest, serve_index};
11
12/// Single Page App (SPA) service builder
13///
14/// # Examples
15/// ```
16/// # use actix_web::App;
17/// # use actix_web_nextjs::spa;
18///
19/// let app = App::new()
20///     // API routes and other services
21///     .service(
22///         spa()
23///             .index_file("dist/index.html")
24///             .static_resources_mount("dist")
25///             .static_resources_location("/")
26///             .finish()
27///     );
28/// ```
29#[derive(Debug, Clone)]
30pub struct Spa {
31    index_file: Cow<'static, str>,
32    static_resources_mount: Cow<'static, str>,
33    static_resources_location: Cow<'static, str>,
34}
35
36impl Spa {
37    /// Create a new `Spa` instance with default settings
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    /// Set the index file for the SPA
43    pub fn index_file(mut self, index_file: impl Into<String>) -> Self {
44        self.index_file = Cow::Owned(index_file.into());
45        self
46    }
47
48    /// Set the mount point for static resources
49    pub fn static_resources_mount(mut self, static_resources_mount: impl Into<String>) -> Self {
50        self.static_resources_mount = Cow::Owned(static_resources_mount.into());
51        self
52    }
53
54    /// Set the location for static resources
55    pub fn static_resources_location(mut self, static_resources_location: impl Into<String>) -> Self {
56        self.static_resources_location = Cow::Owned(static_resources_location.into());
57        self
58    }
59
60    /// Finalize the configuration and return the SPA service
61    pub fn finish(self) -> impl HttpServiceFactory {
62        let index_file = Arc::new(self.index_file.into_owned());
63        let static_resources_location = Arc::new(self.static_resources_location.into_owned());
64        let static_resources_mount = self.static_resources_mount.into_owned();
65
66        let path_tree = match find_and_parse_build_manifest(&static_resources_location) {
67            Ok(tree) => Arc::new(tree),
68            Err(e) => {
69                warn!("Failed to parse build manifest: {}. Using default path tree.", e);
70                Arc::new(PathTree::default())
71            }
72        };
73
74        let files = Files::new(&static_resources_mount, static_resources_location.as_str())
75            .index_file("extremely-unlikely-to-exist-!@$%^&*.txt")
76            .default_handler({
77                let index_file = Arc::clone(&index_file);
78                let static_resources_location = Arc::clone(&static_resources_location);
79                let path_tree = Arc::clone(&path_tree);
80
81                move |req| {
82                    let index_file = Arc::clone(&index_file);
83                    let static_resources_location = Arc::clone(&static_resources_location);
84                    let path_tree = Arc::clone(&path_tree);
85
86                    async move {
87                        serve_index(req, &index_file, &static_resources_location, &path_tree)
88                            .await
89                            .map_err(actix_web::Error::from)
90                    }
91                }
92            });
93
94        SpaService {
95            index_file,
96            static_resources_location,
97            files,
98            path_tree,
99        }
100    }
101}
102
103/// Default implementation for `Spa`
104impl Default for Spa {
105    fn default() -> Self {
106        Self {
107            index_file: Cow::Borrowed("./index.html"),
108            static_resources_mount: Cow::Borrowed("/"),
109            static_resources_location: Cow::Borrowed("./"),
110        }
111    }
112}
113
114/// Helper function to create a default `Spa` instance
115pub fn spa() -> Spa {
116    Spa::default()
117}