use std::{env, fs::File, io::Read, path::Path};
use vite_static_shared::{ManifestChunk, ViteManifest};
use anyhow::Context;
use base64::prelude::*;
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote};
use syn::LitByteStr;
fn vec_to_tokens<T: ToTokens>(data: &[T]) -> TokenStream {
quote! { &[ #(#data),* ] }
}
fn option_to_tokens<T: ToTokens>(data: &Option<T>) -> TokenStream {
if let Some(data) = data {
quote! { Some(#data) }
} else {
quote! { None }
}
}
fn construct_chunk<S: ToTokens>(chunk: &ManifestChunk<S>) -> TokenStream {
let key = &chunk.key;
let contents = LitByteStr::new(&chunk.contents, Span::call_site());
let hash = &chunk.hash;
let mime_type = &chunk.mime_type;
let src = option_to_tokens(&chunk.src);
let file = &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::StaticManifestChunk {
key: #key,
contents: #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,
}
}
}
pub fn get_manifest(dir: &str) -> anyhow::Result<ViteManifest> {
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 file = File::open(vite_dist.join(".vite").join("manifest.json"))
.context("failed to open vite manifest")?;
let mut manifest: ViteManifest =
serde_json::from_reader(file).context("failed to parse vite manifest")?;
for chunk in manifest.clone().values() {
for css in &chunk.css {
manifest.insert(
css.clone(),
ManifestChunk {
key: css.clone(),
file: css.clone(),
..Default::default()
},
);
}
}
for (key, chunk) in &mut manifest {
chunk.key = key.clone();
let mut file = File::open(vite_dist.join(&chunk.file)).with_context(|| {
format!(
r#"failed to open vite chunk at "{path}""#,
path = chunk.file
)
})?;
file.read_to_end(&mut chunk.contents).with_context(|| {
format!(
r#"failed to read vite chunk at "{path}""#,
path = chunk.file
)
})?;
chunk.hash = {
let hash = blake3::hash(&chunk.contents);
BASE64_STANDARD.encode(hash.as_bytes())
};
chunk.mime_type = mime_guess::from_path(&chunk.file)
.first_or_octet_stream()
.to_string();
}
Ok(manifest)
}
pub 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)
}