mod graph;
mod json;
mod metafile;
mod payload;
mod render;
mod serialize;
mod traversal;
use std::collections::HashSet;
use webui_protocol::WebUIProtocol;
use crate::{AssetFileNameTemplate, WebUIError};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ComponentAssetFile {
pub name: String,
pub content: String,
}
#[derive(Debug)]
pub struct ComponentAssetGraph {
pub files: Vec<ComponentAssetFile>,
pub metafile: Option<String>,
entry_fragments: Vec<String>,
entry_components: Vec<String>,
}
impl ComponentAssetGraph {
pub(crate) fn retain_entry_protocol(&self, protocol: &mut WebUIProtocol) {
if self.files.is_empty() {
return;
}
protocol
.fragments
.retain(|name, _| self.entry_fragments.binary_search(name).is_ok());
protocol
.components
.retain(|name, _| self.entry_components.binary_search(name).is_ok());
}
}
#[must_use = "component asset graph files must be written or otherwise consumed"]
pub fn render_component_assets(
protocol: &WebUIProtocol,
entry: &str,
roots: &[String],
file_name_template: &str,
emit_metafile: bool,
) -> Result<ComponentAssetGraph, WebUIError> {
if roots.is_empty() {
if emit_metafile {
return Err(WebUIError::InvalidBuildOptions(
"metafile requires at least one component_asset_root".to_string(),
));
}
return Ok(ComponentAssetGraph {
files: Vec::new(),
metafile: None,
entry_fragments: Vec::new(),
entry_components: Vec::new(),
});
}
let file_name_template =
AssetFileNameTemplate::try_new(file_name_template.to_string(), "asset_file_name_template")
.map_err(|error| WebUIError::InvalidBuildOptions(error.to_string()))?;
let plan = graph::plan_component_assets(protocol, entry, roots)?;
let rendered =
render::render_component_asset_graph(protocol, &plan, &file_name_template, emit_metafile)?;
validate_unique_asset_file_names(&rendered.files)?;
let metafile = if emit_metafile {
Some(metafile::render_metafile(protocol, &rendered.outputs)?)
} else {
None
};
Ok(ComponentAssetGraph {
files: rendered.files,
metafile,
entry_fragments: plan.entry_fragments,
entry_components: plan.entry_components,
})
}
fn validate_unique_asset_file_names(files: &[ComponentAssetFile]) -> Result<(), WebUIError> {
let mut names = HashSet::with_capacity(files.len());
for file in files {
if !names.insert(file.name.as_str()) {
return Err(WebUIError::InvalidBuildOptions(format!(
"component asset filename collision for '{}'. Adjust --asset-file-name-template to include [name] or another unique component-specific segment.",
file.name
)));
}
}
Ok(())
}