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
use std::sync::Arc;
use crate::asset::Asset;
use crate::error::ViteError;
use crate::manifest::Manifest;
use crate::utils::map_to_arc_vec;
use crate::config::{ViteConfig, ViteMode};
use crate::CLIENT_SCRIPT_PATH;
#[derive(Debug)]
pub struct Vite {
pub(crate) manifest: Manifest,
pub(crate) entrypoints: Arc<Vec<String>>,
pub(crate) mode: ViteMode,
pub(crate) dev_server_host: String,
}
impl Vite {
/// Creates a new Vite instance.
///
/// # Arguments
/// * `config` - a [`ViteConfig<'_>`] instance.
///
/// # Errors
/// Returns `Err` if the given manifest path is not valid.
///
/// # Example
/// ```rust
/// use vite_rust::{Vite, ViteConfig};
///
/// #[tokio::main]
/// async fn main() {
/// let mut vite_config = ViteConfig::new_with_defaults("tests/test-manifest.json");
/// vite_config.entrypoints = Some(vec!["views/foo.js"]);
/// vite_config.force_mode = Some(vite_rust::ViteMode::Manifest);
///
/// let vite = Vite::new(vite_config).await.unwrap();
///
/// let expected =
/// r#"<link rel="stylesheet" href="assets/foo-5UjPuW-k.css" />
/// <link rel="stylesheet" href="assets/shared-ChJ_j-JJ.css" />
/// <script type="module" src="assets/foo-BRBmoGS9.js"></script>
/// <link rel="modulepreload" href="assets/shared-B7PI925R.js" />"#;
///
/// let expected = expected.replace("\t", " ")
/// .lines()
/// .map(str::trim)
/// .collect::<Vec::<&str>>()
/// .join("\n");
///
/// assert_eq!(vite.get_tags(), expected);
/// }
/// ```
///
/// [`ViteConfig`]: crate::config::ViteConfig
pub async fn new<'b>(config: ViteConfig<'b>) -> Result<Vite, ViteError> {
let manifest = Manifest::new(config.manifest_path)?;
let entrypoints= match config.entrypoints {
Some(entrypoints) => map_to_arc_vec(entrypoints),
None => map_to_arc_vec(manifest.get_manifest_entries()),
};
let dev_host = config.server_host.unwrap_or("http://localhost:5173").to_string();
let mode = match config.force_mode {
Some(mode) => mode,
None => ViteMode::discover(
config.use_heart_beat_check,
config.enable_dev_server,
&dev_host
).await
};
return Ok(Vite {
entrypoints,
manifest,
mode,
dev_server_host: dev_host
});
}
/// Generates assets HTML tags from `manifest.json` file.
///
/// # Panics
/// Might panic if the file doesn't exist.
pub fn get_tags(&self) -> String {
self.manifest.generate_html_tags(&self.entrypoints)
}
/// Generates scripts and stylesheet link HTML tags referencing
/// the entrypoints directly from the Vite dev-server.
pub fn get_development_scripts(&self) -> String {
let mut tags = vec![];
for entry in self.entrypoints.iter() {
if entry.ends_with(".css") {
tags.push(Asset::StyleSheet(self.get_asset_url(entry)).to_html());
} else {
tags.push(Asset::EntryPoint(self.get_asset_url(entry)).to_html());
}
}
return tags.join("\n");
}
/// Generates HTML tags considering the current [`ViteMode`]:
/// - If `Development` mode, calls `Vite::get_development_scripts()` and `Vite::get_hmr_script()`
/// and return a concatenation of their returns;
/// - If `Manifest` mode, calls `Vite::get_tags()` and return the assets HTML tags.
pub fn get_resolved_vite_scripts(&self) -> String {
match self.mode {
ViteMode::Development => format!("{}\n{}", self.get_development_scripts(), self.get_hmr_script()),
ViteMode::Manifest => self.get_tags()
}
}
/// Returns a script tag referencing the Hot Module Reload client script from the Vite dev-server.
///
/// If [`ViteMode`] is set to `Manifest`, only an empty string is returned.
pub fn get_hmr_script(&self) -> String {
match self.mode {
ViteMode::Development => {
format!(
r#"<script type="module" src="{}/{}"></script>"#,
&self.dev_server_host,
CLIENT_SCRIPT_PATH
)
},
ViteMode::Manifest => "".to_string()
}
}
/// Returns the bundled file by the given original file's path. If it is not present in the
/// manifest file, an empty string is returned.
///
/// # Arguments
/// - `path` - the root-relative path to an asset file. E.g. "src/assets/react.svg".
pub fn get_asset_url(&self, path: &str) -> String {
let path = if path.starts_with("/") { &path[1..] } else { path };
let path = path.replace("'", "");
match &self.mode {
ViteMode::Development => format!("{}/{}", self.dev_server_host, path),
ViteMode::Manifest => self.manifest.get_asset_url(&path).to_string(),
}
}
/// Returns the [react fast refresh script] relative to the current Vite dev-server URL.
///
/// [react fast refresh script]: https://vite.dev/guide/backend-integration
pub fn get_react_script(&self) -> String {
return format!(
r#"<script type="module">
import RefreshRuntime from '{}/@react-refresh'
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {{}}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>"#,
&self.dev_server_host
);
}
/// Returns the current `manifest.json` file hash. Might be used for
/// assets versioning.
///
/// The resultant string is a hex-encoded MD5 hash.
#[inline]
pub fn get_hash(&self) -> &str { self.manifest.get_hash() }
/// Returns the Vite instance's dev-server URL.
pub fn get_dev_server_url(&self) -> &str { &self.dev_server_host }
/// Returns the current Vite instance's mode.
pub fn mode(&self) -> &ViteMode { &self.mode }
}