Skip to main content

webui_wasm/
parser.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT license.
3
4//! Parser-only WASM exports.
5
6use 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/// Build protocol protobuf bytes from virtual files without rendering.
14///
15/// Returns the serialized `WebUIProtocol` as protobuf bytes.
16#[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
32/// Register all component `.html` files and optional companion `.css` files
33/// from the virtual file map, skipping the entry.
34fn 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
46                    .component_registry_mut()
47                    .register_component(tag_name, content, css)?;
48            }
49        }
50    }
51    Ok(())
52}
53
54/// Parse virtual files into a `WebUIProtocol` using the real `webui-parser`
55/// with the WebUI plugin.
56pub(crate) fn parse_to_protocol(
57    files: &HashMap<String, String>,
58    entry: &str,
59) -> Result<WebUIProtocol, WasmError> {
60    let entry_html = files
61        .get(entry)
62        .ok_or_else(|| WasmError::MissingEntry(entry.to_string()))?;
63
64    let mut parser =
65        HtmlParser::with_plugin_options(Box::new(WebUIParserPlugin::new()), CssStrategy::Style);
66    register_components(&mut parser, files, entry)?;
67    parser.parse(entry, entry_html)?;
68    parser.take_plugin_artifacts()?;
69
70    Ok(WebUIProtocol::new(parser.into_fragment_records()))
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn build_protocol_reports_missing_entry() {
79        let files = HashMap::new();
80        let err = build_protocol_inner(&files, "index.html").unwrap_err();
81        assert_eq!(err.to_string(), "Entry file 'index.html' not found");
82    }
83}