use zdc_codegen::{Bundle, Options};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BundleSize {
pub name: String,
pub client_js: usize,
pub boot_js: usize,
pub styles_css: usize,
pub index_html: usize,
pub manifest_json: usize,
}
impl BundleSize {
pub fn total(&self) -> usize {
self.client_js + self.boot_js + self.styles_css + self.index_html + self.manifest_json
}
}
pub fn repository_path(relative: &str) -> std::path::PathBuf {
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join(relative)
}
pub fn try_compile(source: &str, name: &str) -> Result<Bundle, Vec<String>> {
let program = zdc_parser::parse(source).map_err(|e| vec![e.message])?;
let hir = zdc_resolve::Resolver::new(&program)
.resolve()
.map_err(|errors| errors.into_iter().map(|e| e.message).collect::<Vec<_>>())?;
let split = zdc_graph::split(&hir);
if split.has_errors() {
return Err(split.errors().map(|error| error.message.clone()).collect());
}
let verdict = zdc_graph::ifc(&hir, &split);
let table = zdc_types::check(&hir, &split)
.map_err(|errors| errors.into_iter().map(|e| e.message).collect::<Vec<_>>())?;
let Some(cleared) = verdict.clearance() else {
return Err(verdict
.errors()
.map(|error| error.message.clone())
.collect());
};
let options = Options::new(name, "bench");
let inputs = zdc_codegen::Inputs {
hir: &hir,
split: &split,
verdict: &verdict,
table: &table,
cleared,
};
zdc_codegen::compile(&inputs, &options)
.map_err(|errors| errors.into_iter().map(|e| e.message).collect())
}
pub fn compile(relative: &str) -> Bundle {
let source = std::fs::read_to_string(repository_path(relative))
.unwrap_or_else(|e| panic!("reading {relative}: {e}"));
try_compile(&source, relative).unwrap_or_else(|errors| {
panic!(
"{relative} failed to compile:\n{}",
errors
.iter()
.map(|message| format!(" {message}"))
.collect::<Vec<_>>()
.join("\n")
)
})
}
pub fn bundle_sizes() -> Vec<BundleSize> {
[
"examples/hello.zd",
"examples/counter.zd",
"crates/zdc-bench/bench/row.zd",
]
.into_iter()
.map(|relative| {
let bundle = compile(relative);
BundleSize {
name: relative.to_string(),
client_js: bundle.client_js.len(),
boot_js: bundle.boot_js.as_ref().map_or(0, String::len),
styles_css: bundle.styles_css.len(),
index_html: bundle.index_html.as_ref().map_or(0, String::len),
manifest_json: bundle.manifest_json.len(),
}
})
.collect()
}
pub fn runtime_sizes() -> Vec<(&'static str, usize)> {
let shipped = |source| zdc_runtime::for_mode(source, zdc_runtime::Mode::Release).len();
vec![
("runtime/signal.js", shipped(zdc_runtime::SIGNAL_JS)),
("runtime/dom.js", shipped(zdc_runtime::DOM_JS)),
(
"runtime/foreign.js (a gives-view foreign only)",
shipped(zdc_runtime::FOREIGN_JS),
),
(
"runtime/markup.js (a program with Prose only)",
shipped(zdc_runtime::MARKUP_JS),
),
(
"runtime/list.js (a program with an each only)",
shipped(zdc_runtime::LIST_JS),
),
("runtime/base.css", zdc_runtime::BASE_CSS.len()),
(
"runtime/elements.js (direct emission only)",
shipped(zdc_runtime::ELEMENTS_JS),
),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_listed_example_compiles_and_is_not_empty() {
let sizes = bundle_sizes();
assert_eq!(sizes.len(), 3, "three examples are listed: {sizes:?}");
for size in sizes {
assert!(size.client_js > 0, "{} emitted nothing", size.name);
assert!(size.total() > size.client_js);
}
}
}