webui/
component_assets.rs1mod graph;
7mod json;
8mod metafile;
9mod payload;
10mod render;
11mod serialize;
12mod traversal;
13
14use std::collections::HashSet;
15use webui_protocol::WebUIProtocol;
16
17use crate::{AssetFileNameTemplate, WebUIError};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct ComponentAssetFile {
22 pub name: String,
24 pub content: String,
26}
27
28#[derive(Debug)]
30pub struct ComponentAssetGraph {
31 pub files: Vec<ComponentAssetFile>,
33 pub metafile: Option<String>,
35 entry_fragments: Vec<String>,
36 entry_components: Vec<String>,
37}
38
39impl ComponentAssetGraph {
40 pub(crate) fn retain_entry_protocol(&self, protocol: &mut WebUIProtocol) {
41 if self.files.is_empty() {
42 return;
43 }
44 protocol
45 .fragments
46 .retain(|name, _| self.entry_fragments.binary_search(name).is_ok());
47 protocol
48 .components
49 .retain(|name, _| self.entry_components.binary_search(name).is_ok());
50 }
51}
52
53#[must_use = "component asset graph files must be written or otherwise consumed"]
64pub fn render_component_assets(
65 protocol: &WebUIProtocol,
66 entry: &str,
67 roots: &[String],
68 file_name_template: &str,
69 emit_metafile: bool,
70) -> Result<ComponentAssetGraph, WebUIError> {
71 if roots.is_empty() {
72 if emit_metafile {
73 return Err(WebUIError::InvalidBuildOptions(
74 "metafile requires at least one component_asset_root".to_string(),
75 ));
76 }
77 return Ok(ComponentAssetGraph {
78 files: Vec::new(),
79 metafile: None,
80 entry_fragments: Vec::new(),
81 entry_components: Vec::new(),
82 });
83 }
84
85 let file_name_template =
86 AssetFileNameTemplate::try_new(file_name_template.to_string(), "asset_file_name_template")
87 .map_err(|error| WebUIError::InvalidBuildOptions(error.to_string()))?;
88 let plan = graph::plan_component_assets(protocol, entry, roots)?;
89 let rendered =
90 render::render_component_asset_graph(protocol, &plan, &file_name_template, emit_metafile)?;
91 validate_unique_asset_file_names(&rendered.files)?;
92 let metafile = if emit_metafile {
93 Some(metafile::render_metafile(protocol, &rendered.outputs)?)
94 } else {
95 None
96 };
97
98 Ok(ComponentAssetGraph {
99 files: rendered.files,
100 metafile,
101 entry_fragments: plan.entry_fragments,
102 entry_components: plan.entry_components,
103 })
104}
105
106fn validate_unique_asset_file_names(files: &[ComponentAssetFile]) -> Result<(), WebUIError> {
107 let mut names = HashSet::with_capacity(files.len());
108 for file in files {
109 if !names.insert(file.name.as_str()) {
110 return Err(WebUIError::InvalidBuildOptions(format!(
111 "component asset filename collision for '{}'. Adjust --asset-file-name-template to include [name] or another unique component-specific segment.",
112 file.name
113 )));
114 }
115 }
116 Ok(())
117}