vite-static-derive 0.5.0

Derive macro support for `vite-static`
Documentation
use std::{borrow::Cow, env, path::Path};

use vite_static_shared::{
    ManifestChunk, ViteManifest,
    parsing::{parse_manifest, read_manifest_chunk},
};

use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{Ident, LitByteStr};

use crate::parse::ManifestSettings;

fn str_to_tokens(data: &str) -> TokenStream {
    quote! { Cow::Borrowed(#data) }
}

fn vec_to_tokens(data: &[Cow<'_, str>]) -> TokenStream {
    let items = data.iter().map(|i| str_to_tokens(i));
    quote! { Cow::Borrowed(&[ #(#items),* ]) }
}

fn option_to_tokens(data: &Option<Cow<'_, str>>) -> TokenStream {
    if let Some(data) = data {
        let data = str_to_tokens(data);
        quote! { Some(#data) }
    } else {
        quote! { None }
    }
}

fn construct_chunk(chunk: &ManifestChunk<'_>) -> TokenStream {
    let key = str_to_tokens(&chunk.key);
    let contents = LitByteStr::new(&chunk.contents, Span::call_site());
    let hash = str_to_tokens(&chunk.hash);
    let mime_type = str_to_tokens(&chunk.mime_type);

    let src = option_to_tokens(&chunk.src);
    let file = str_to_tokens(&chunk.file);
    let css = vec_to_tokens(&chunk.css);
    let assets = vec_to_tokens(&chunk.assets);
    let is_entry = &chunk.is_entry;
    let name = option_to_tokens(&chunk.name);
    let is_dynamic_entry = &chunk.is_dynamic_entry;
    let imports = vec_to_tokens(&chunk.imports);
    let dynamic_imports = vec_to_tokens(&chunk.dynamic_imports);

    quote! {
        ::vite_static::ManifestChunk::<'static> {
            key: #key,
            contents: Cow::Borrowed(#contents),
            hash: #hash,
            mime_type: #mime_type,

            src: #src,
            file: #file,
            css: #css,
            assets: #assets,
            is_entry: #is_entry,
            name: #name,
            is_dynamic_entry: #is_dynamic_entry,
            imports: #imports,
            dynamic_imports: #dynamic_imports,
        }
    }
}

fn get_manifest(dir: &str) -> anyhow::Result<ViteManifest<'static>> {
    // TODO: fix microslop windows paths
    let vite_dist = if dir.starts_with('/') {
        Path::new(dir).to_path_buf()
    } else {
        let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
        let current_dir = Path::new(&cargo_manifest_dir);

        current_dir.join(dir)
    };

    let mut manifest = parse_manifest(&vite_dist)?;

    for chunk in manifest.values_mut() {
        read_manifest_chunk(&vite_dist, chunk)?;
    }

    Ok(manifest)
}

fn generate_manifest_data(manifest: &ViteManifest<'_>) -> (Vec<TokenStream>, Vec<TokenStream>) {
    let mut manifest_items = manifest.iter().collect::<Vec<_>>();

    let key_output_pairs = {
        manifest_items.sort_unstable_by_key(|&(key, _)| key);

        manifest_items
            .iter()
            .map(|&(key, chunk)| {
                let output = &chunk.file;
                quote! { (#key, #output) }
            })
            .collect()
    };

    let chunks = {
        manifest_items.sort_unstable_by_key(|&(_, chunk)| &chunk.file);

        manifest_items
            .iter()
            .map(|&(_, chunk)| {
                let output = &chunk.file;
                let chunk = construct_chunk(chunk);
                quote! { (#output, &#chunk) }
            })
            .collect()
    };

    (key_output_pairs, chunks)
}

pub fn generate(ident: &Ident, settings: &ManifestSettings) -> syn::Result<TokenStream> {
    let manifest = get_manifest(&settings.vite_dist.0)
        .map_err(|err| syn::Error::new(settings.vite_dist.1, format!("{err:#}")))?;

    let (key_output_pairs, chunks) = generate_manifest_data(&manifest);

    let key_output_pairs_ident = format_ident!("{}_KEY_OUTPUT_PAIRS", ident);
    let chunks_ident = format_ident!("{}_CHUNKS", ident);

    Ok(quote! {
        use ::std::borrow::Cow;

        #[allow(non_upper_case_globals)]
        const #key_output_pairs_ident: &[(&'static str, &'static str)] = &[ #(#key_output_pairs),* ];
        #[allow(non_upper_case_globals)]
        const #chunks_ident: &[(&'static str, &'static ::vite_static::ManifestChunk<'static>)] = &[ #(#chunks),* ];

        impl ::vite_static::StaticManifest for #ident {
            fn static_key_output_pairs(&self) -> &[(&'static str, &'static str)] { #key_output_pairs_ident }
            fn static_chunks(&self) -> &[(&'static str, &'static ::vite_static::ManifestChunk<'static>)] { #chunks_ident }
        }
    })
}