Skip to main content

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}