vite_static_shared/parsing.rs
1//! Vite manifest parsing module.
2//!
3//! This module contains all required functions to parse manifest and read chunks, so it's sort of
4//! "internal", that you probably shouldn't use.
5//!
6//! **To parse and automatically implement traits, use `Manifest` derive!**
7
8use std::{borrow::Cow, fs::File, io::Read as _, path::Path};
9
10use anyhow::Context as _;
11use anyhow::Result;
12use base64::{Engine as _, prelude::BASE64_STANDARD};
13
14use crate::{ManifestChunk, ViteManifest};
15
16/// Parses Vite manifest and returns it.
17///
18/// Takes Vite Project's dist folder and returns [`ViteManifest`].
19///
20/// ```
21/// # use vite_static_shared::parsing::*;
22/// # use std::path::Path;
23/// #
24/// parse_manifest(&Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/examples/vite-project/dist")));
25/// ```
26///
27/// # Errors
28///
29/// - `failed to open vite manifest` - cannot open `.vite/manifest.json`
30/// - `failed to parse vite manifest` - invalid `.vite/manifest.json`
31pub fn parse_manifest(vite_dist: &Path) -> anyhow::Result<ViteManifest<'static>> {
32 let file = File::open(vite_dist.join(".vite").join("manifest.json"))
33 .context("failed to open vite manifest")?;
34
35 let mut manifest: ViteManifest =
36 serde_json::from_reader(file).context("failed to parse vite manifest")?;
37
38 // for whatever reason, "cssCodeSplit" doesn't add splitted CSS files into vite manifest as chunks/assets,
39 // to fix it, we are manually add all referenced CSS files into manifest
40 for chunk in manifest.clone().values() {
41 for css in chunk.css.as_ref() {
42 manifest.insert(
43 css.clone(),
44 ManifestChunk {
45 key: css.clone(),
46 file: css.clone(),
47 ..Default::default()
48 },
49 );
50 }
51 }
52
53 for (key, chunk) in &mut manifest {
54 chunk.key.clone_from(key);
55 }
56
57 Ok(manifest)
58}
59
60/// Reads contents of chunk, computes hash and guesses `mime_type`, and writes into [`ManifestChunk`].
61///
62/// ```ignore
63/// let SomeManifestChunk = ManifestChunk { ... };
64/// read_manifest_chunk("/path/to/vite-project/dist", &mut SomeManifestChunk)?;
65/// ```
66///
67/// # Errors
68///
69/// - `failed to open vite chunk at "..."`
70/// - `failed to read vite chunk at "..."`
71pub fn read_manifest_chunk(vite_dist: &Path, chunk: &mut ManifestChunk) -> Result<()> {
72 let mut file = File::open(vite_dist.join(chunk.file.as_ref())).with_context(|| {
73 format!(
74 r#"failed to open vite chunk at "{path}""#,
75 path = chunk.file
76 )
77 })?;
78
79 let mut contents = Vec::new();
80
81 file.read_to_end(&mut contents).with_context(|| {
82 format!(
83 r#"failed to read vite chunk at "{path}""#,
84 path = chunk.file
85 )
86 })?;
87
88 chunk.hash = Cow::Owned({
89 let hash = blake3::hash(&contents);
90 BASE64_STANDARD.encode(hash.as_bytes())
91 });
92
93 chunk.contents = Cow::Owned(contents);
94
95 chunk.mime_type = mime_guess::from_path(chunk.file.as_ref())
96 .first_or_octet_stream()
97 .to_string()
98 .into();
99
100 Ok(())
101}