1use crate::error::WasmError;
7use std::collections::HashMap;
8use wasm_bindgen::prelude::*;
9use webui_parser::plugin::webui::WebUIParserPlugin;
10use webui_parser::{CssStrategy, HtmlParser};
11use webui_protocol::WebUIProtocol;
12
13#[wasm_bindgen]
17pub fn build_protocol(files: JsValue, entry: &str) -> Result<Vec<u8>, JsValue> {
18 let files_map: HashMap<String, String> =
19 serde_wasm_bindgen::from_value(files).map_err(|e| JsValue::from_str(&e.to_string()))?;
20
21 build_protocol_inner(&files_map, entry).map_err(|e| JsValue::from_str(&e.to_string()))
22}
23
24pub(crate) fn build_protocol_inner(
25 files: &HashMap<String, String>,
26 entry: &str,
27) -> Result<Vec<u8>, WasmError> {
28 let protocol = parse_to_protocol(files, entry)?;
29 protocol.to_protobuf().map_err(WasmError::Protocol)
30}
31
32fn register_components(
35 parser: &mut HtmlParser,
36 files: &HashMap<String, String>,
37 entry: &str,
38) -> Result<(), WasmError> {
39 for (filename, content) in files {
40 if filename != entry && filename.ends_with(".html") {
41 let tag_name = filename.trim_end_matches(".html");
42 if tag_name.contains('-') {
43 let css_key = format!("{tag_name}.css");
44 let css = files.get(&css_key).map(String::as_str);
45 parser.component_registry_mut().register_component(
46 tag_name,
47 content,
48 css,
49 has_script(files, tag_name),
50 )?;
51 }
52 }
53 }
54 Ok(())
55}
56
57fn has_script(files: &HashMap<String, String>, tag_name: &str) -> bool {
58 files.contains_key(&format!("{tag_name}.ts")) || files.contains_key(&format!("{tag_name}.js"))
59}
60
61pub(crate) fn parse_to_protocol(
64 files: &HashMap<String, String>,
65 entry: &str,
66) -> Result<WebUIProtocol, WasmError> {
67 let entry_html = files
68 .get(entry)
69 .ok_or_else(|| WasmError::MissingEntry(entry.to_string()))?;
70
71 let mut parser =
72 HtmlParser::with_plugin_options(Box::new(WebUIParserPlugin::new()), CssStrategy::Style);
73 register_components(&mut parser, files, entry)?;
74 parser.parse(entry, entry_html)?;
75 parser.take_plugin_artifacts()?;
76
77 Ok(WebUIProtocol::new(parser.into_fragment_records()))
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn build_protocol_reports_missing_entry() {
86 let files = HashMap::new();
87 let err = build_protocol_inner(&files, "index.html").unwrap_err();
88 assert_eq!(err.to_string(), "Entry file 'index.html' not found");
89 }
90}