vite_rust/
config.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
use std::env;

use crate::utils::check_heart_beat;

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ViteMode {
    Development,
    Manifest,
}

impl ViteMode {
    pub(crate) async fn discover(
        use_hb: bool,
        use_dev_server: bool,
        host: &str,
        hb_retries: u8,
    ) -> ViteMode {
        if !use_hb {
            return ViteMode::Development;
        }

        if !use_dev_server {
            return ViteMode::Manifest;
        }

        if is_production() {
            return ViteMode::Manifest;
        }

        let dev_server_is_ok = check_heart_beat(host, None, hb_retries).await;

        match dev_server_is_ok {
            true => ViteMode::Development,
            false => ViteMode::Manifest,
        }
    }
}

fn is_production() -> bool {
    env::vars().any(|(k, v)| {
        if ["RUST_ENV", "NODE_ENV", "APP_ENV", "__TEST_APP_ENV"].contains(&k.as_str()) {
            return v == "production";
        }

        false
    })
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ViteConfig<'a> {
    /// The `path/to/manifest.json` file.
    /// Currently, Vite won't resolve relative paths, so please consider
    /// your current working directory as the root of it and start the path
    /// with a root node directory directly.
    ///
    /// **Optionally and experimentally**, you can use the [`resolve_path`]
    /// method for the manifest file resolution. However, this method might
    /// come to fail at some point, and will also panic in the many situations
    /// described on its documentation.
    ///
    /// # Example
    /// ```plaintext
    /// your_project/
    /// |-- public/
    /// |   |-- dist/
    /// |   |   |-- manifest.json
    /// |-- src/
    /// |   |-- main.rs // <-- you're here!
    /// ```
    ///
    /// ```ignore
    ///
    /// use vite_rust::{ViteConfig, utils::resolve_path};
    ///
    /// let config = ViteConfig {
    ///     manifest_path: Some("public/dist/manifest.json"),
    ///     // or
    ///     manifest_path: Some(resolve_path(file!(), "../public/dist/manifest.json")),
    ///     // ...
    /// };
    /// ```
    pub manifest_path: Option<&'a str>,
    /// Defines which entrypoints Vite will use to generate the html `script`,
    /// `link` and `stylesheet` tags.
    ///
    /// If `None` is provided, Vite will scan the manifest for files with
    /// `isEntry: true` property and consider them the entrypoints.
    pub entrypoints: Option<Vec<&'a str>>,
    /// If `None` is provided, Vite will discover which one to use considering:
    /// -   any of `RUST_ENV`, `NODE_ENV` or `APP_ENV` environment variables exists
    ///     and is set to `true`;
    /// -   Dev-server is running;
    /// -   Heart beat check is enabled.
    ///
    /// By setting this option, the discovering phase will be skipped.
    /// Refer to the crate's `README.md` file to understand the way it decides which mode to pick.
    pub force_mode: Option<ViteMode>,
    /// Whether Vite should ping your vite dev-server to check if its running.
    /// If false, `ViteMode` will be set to `Development` if not forced by the configuration.
    pub use_heart_beat_check: bool,
    /// How many times heartbeat checker should try before fallbacking.
    pub heart_beat_retries_limit: Option<u8>,
    /// Whether dev server should be considered or not.
    ///
    /// If false, `force_mode` should be either `Manifest` or `None`,
    /// otherwise, undefined behavior might occur.
    pub enable_dev_server: bool,
    /// The host in which your vite dev-server is running.
    /// Normally, it would be `"http://localhost:5173"`.
    ///
    /// Please, do not forget the protocol (http, https)!
    pub server_host: Option<&'a str>,
}

impl<'a> ViteConfig<'a> {
    /// Creates a new `ViteConfig` instance with `manifest_path` and `entrypoints` fields set.
    pub fn new(manifest_path: &'a str, entrypoints: Vec<&'a str>) -> Self {
        ViteConfig::default()
            .set_manifest_path(manifest_path)
            .set_entrypoints(entrypoints)
    }

    pub fn set_manifest_path(mut self, manifest_path: &'a str) -> Self {
        self.manifest_path = Some(manifest_path);
        self
    }

    pub fn set_entrypoints(mut self, entrypoints: Vec<&'a str>) -> Self {
        self.entrypoints = Some(entrypoints);
        self
    }

    pub fn set_force_mode(mut self, mode: ViteMode) -> Self {
        self.force_mode = Some(mode);
        self
    }
    pub fn set_server_host(mut self, server_host: &'a str) -> Self {
        self.server_host = Some(server_host);
        self
    }

    pub fn set_heart_beat_retries_limit(mut self, limit: u8) -> Self {
        self.heart_beat_retries_limit = Some(limit);
        self
    }

    pub fn without_heart_beat_check(mut self) -> Self {
        self.use_heart_beat_check = false;
        self
    }

    pub fn without_dev_server(mut self) -> Self {
        self.enable_dev_server = false;
        self
    }
}

impl Default for ViteConfig<'_> {
    /// Create a `ViteConfig` instance.
    ///
    /// You can create your config by directly instantiating the struct, or
    /// by using some default options. Note that you **must set the manifest
    /// path**.
    ///
    /// # Example
    /// ```rust
    /// use vite_rust::ViteConfig;
    ///
    /// let manual_config = ViteConfig {
    ///     manifest_path: Some("path/to/manifest.json"),
    ///     entrypoints: None, // Vite can discover them by itself
    ///     force_mode: None, // Vite can discover it too
    ///     use_heart_beat_check: true,
    ///     enable_dev_server: true,
    ///     server_host: Some("http://localhost:5173"),
    ///     heart_beat_retries_limit: Some(5),
    /// };
    ///
    /// let with_defaults_config = ViteConfig::default().set_manifest_path("path/to/manifest.json");
    ///
    /// assert_eq!(manual_config, with_defaults_config);
    /// ```
    fn default() -> Self {
        Self {
            enable_dev_server: true,
            entrypoints: None,
            manifest_path: None,
            force_mode: None,
            server_host: Some("http://localhost:5173"),
            use_heart_beat_check: true,
            heart_beat_retries_limit: Some(5),
        }
    }
}

#[cfg(test)]
mod test {
    use std::env;

    use crate::{config::is_production, ViteMode};

    #[tokio::test]
    async fn test_discover() {
        let host = "http://localhost:3000";
        let hb_retries = 1;

        assert_eq!(
            ViteMode::Development,
            ViteMode::discover(false, true, host, hb_retries).await
        );

        assert_eq!(
            ViteMode::Manifest,
            ViteMode::discover(true, false, host, hb_retries).await
        );
    }

    #[test]
    fn test_is_production() {
        env::set_var("__TEST_APP_ENV", "production");
        assert!(is_production());

        env::remove_var("__TEST_APP_ENV");
        assert!(!is_production());
    }
}