vite-static-shared 0.5.0

Shared code for vite-static
Documentation
#![warn(clippy::pedantic)]

use std::{borrow::Cow, collections::HashMap};

use serde::{Deserialize, Serialize};

pub mod parsing;

/// Type of `.vite/manifest.json`.
pub type ViteManifest<'a> = HashMap<Cow<'a, str>, ManifestChunk<'a>>;

/// Vite manifest chunk.
///
/// [See Vite documentation](https://vite.dev/guide/backend-integration)
#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct ManifestChunk<'a> {
    /// The key of this chunk in manifest.
    ///
    /// This field is added by `vite-static` and skipped during (de)serialization.
    #[serde(skip)]
    pub key: Cow<'a, str>,

    /// The contents of manifest chunk.
    ///
    /// This field is added by `vite-static` and skipped during (de)serialization.
    #[serde(skip)]
    pub contents: Cow<'a, [u8]>,

    /// Hash of this chunk. Derive macro uses [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) as hashing algorithm.
    ///
    /// This field is added by `vite-static` and skipped during (de)serialization.
    #[serde(skip)]
    pub hash: Cow<'a, str>,

    /// Mime type of this chunk.
    ///
    /// This field is added by `vite-static` and skipped during (de)serialization.
    #[serde(skip)]
    pub mime_type: Cow<'a, str>,

    /// The input file name of this chunk/asset if known.
    pub src: Option<Cow<'a, str>>,

    /// The output file name of this chunk/asset.
    pub file: Cow<'a, str>,

    /// The list of CSS files imported by this chunk.
    #[serde(default)]
    pub css: Cow<'a, [Cow<'a, str>]>,

    /// The list of asset files imported by this chunk, excluding CSS files.
    #[serde(default)]
    pub assets: Cow<'a, [Cow<'a, str>]>,

    /// Whether this chunk or asset is an entry point
    #[serde(default)]
    pub is_entry: bool,

    /// The name of this chunk/asset if known.
    pub name: Option<Cow<'a, str>>,

    /// Whether this chunk is a dynamic entry point
    ///
    /// This field is only present in JS chunks.
    #[serde(default)]
    pub is_dynamic_entry: bool,

    /// The list of statically imported chunks by this chunk
    ///
    /// The values are the keys of the manifest. This field is only present in JS chunks.
    #[serde(default)]
    pub imports: Cow<'a, [Cow<'a, str>]>,

    /// The list of dynamically imported chunks by this chunk
    ///
    /// The values are the keys of the manifest. This field is only present in JS chunks.
    #[serde(default)]
    pub dynamic_imports: Cow<'a, [Cow<'a, str>]>,
}

/// Trait, that returns boxed [`Manifest`].
///
/// This trait is required for manifests and usually by integrations. **It is automatically implemented by derive macro**.
///
/// ```
/// impl Boxed<'static> for MyViteManifest {
///     fn boxed(&self) -> Box<dyn Manifest<'static>> {
///         Box::new(MyViteManifest)
///     }
/// }
///
/// SomeCoolAndAwesomeIntegration::new(MyViteManifest.boxed()) // easy and simple!
/// ```
pub trait Boxed<'a> {
    fn boxed(&self) -> Box<dyn Manifest<'a>>;
}

/// Trait for static/embedded manifests.
///
/// This trait is sort of internal, because it's suitable **only for embedded manifests**, so use [`Manifest`] instead!
///
/// This trait automatically implements [`Manifest`].
pub trait StaticManifest: Boxed<'static> {
    /// Get array of `(key, output_filename)` pairs.
    ///
    /// Array must be sorted by `key`
    fn static_key_output_pairs(&self) -> &[(&'static str, &'static str)];

    /// Get [`ManifestChunk`]s as `(output_filename, chunk)` pairs.
    ///
    /// Array must be sorted by `output_filename`
    fn static_chunks(&self) -> &[(&'static str, &'static ManifestChunk<'static>)];
}

/// Trait, that helps query chunks in Vite manifest.
///
/// See `Manifest` derive and [`ManifestChunk`].
pub trait Manifest<'a>: Boxed<'a> {
    /// Get `output_filename` from manifest key.
    ///
    /// ```rust
    /// MyViteManifest.resolve_output("src/main.tsx") // returns "main.HASH.js"
    /// ```
    fn resolve_output(&self, key: &str) -> Option<Cow<'a, str>>;

    /// Iterator over manifest keys (input filenames, e.g. "src/main.tsx").
    ///
    /// ```rust
    /// MyViteManifest.iter_keys()
    /// // returns iterator over keys (e.g. "src/main.tsx", "src/component.tsx", ...)
    /// ```
    fn iter_keys(&self) -> Box<dyn Iterator<Item = &str> + '_>;

    /// Iterator over manifest outputs (output filenames, e.g. "main.hashhh.js").
    ///
    /// ```rust
    /// MyViteManifest.iter_outputs()
    /// // returns iterator over output files (e.g. "main.HASH.js", "chunks/_shared.HASH.js", ...)
    /// ```
    fn iter_outputs(&self) -> Box<dyn Iterator<Item = &str> + '_>;

    /// Get [`ManifestChunk`] from output filename.
    ///
    /// ```rust
    /// dbg!(MyViteManifest.chunk("main.HASH.js"))
    /// ```
    fn chunk(&self, output: &str) -> Option<Cow<'a, ManifestChunk<'a>>>;

    /// Get [`ManifestChunk`] from manifest key.
    ///
    /// ```rust
    /// dbg!(MyViteManifest.chunk_by_key("src/main.tsx"))
    /// ```
    fn chunk_by_key(&self, key: &str) -> Option<Cow<'a, ManifestChunk<'a>>> {
        match self.resolve_output(key) {
            Some(output) => self.chunk(&output),
            None => None,
        }
    }
}

impl<M: StaticManifest> Manifest<'static> for M {
    fn resolve_output(&self, key: &str) -> Option<Cow<'static, str>> {
        let pairs = self.static_key_output_pairs();

        match pairs.binary_search_by_key(&key, |(this_key, _)| this_key) {
            Ok(index) => Some(Cow::Borrowed(pairs[index].1)),
            Err(_) => None,
        }
    }

    fn iter_keys(&self) -> Box<dyn Iterator<Item = &str> + '_> {
        Box::new(self.static_key_output_pairs().iter().map(|&(key, _)| key))
    }

    fn iter_outputs(&self) -> Box<dyn Iterator<Item = &str> + '_> {
        Box::new(
            self.static_key_output_pairs()
                .iter()
                .map(|&(_, output)| output),
        )
    }

    fn chunk(&self, output: &str) -> Option<Cow<'static, ManifestChunk<'static>>> {
        let chunks = self.static_chunks();

        match chunks.binary_search_by_key(&output, |(this_output, _)| this_output) {
            Ok(index) => Some(Cow::Borrowed(chunks[index].1)),
            Err(_) => None,
        }
    }
}