// Copyright 2018-2025 the Deno authors. MIT license. use std::borrow::Cow; use std::cell::Cell; use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; use capacity_builder::StringBuilder; use deno_core::anyhow; use deno_core::error::AnyError; use lol_html::element; use lol_html::html_content::ContentType as LolContentType; use crate::tools::bundle::OutputFile; #[derive(Debug, Clone)] pub struct Script { pub src: Option, pub is_async: bool, pub is_module: bool, pub resolved_path: Option, } struct Attr<'a> { name: Cow<'static, str>, value: Option>, } impl<'a> Attr<'a> { fn new( name: impl Into>, value: Option>, ) -> Self { Self { name: name.into(), value, } } fn write_out<'s>(&'s self, out: &mut StringBuilder<'s>) where 'a: 's, { out.append(&self.name); if let Some(value) = &self.value { out.append("=\""); out.append(value); out.append('"'); } } } fn write_attr_list<'a, 's>(attrs: &'s [Attr<'a>], out: &mut StringBuilder<'s>) where 'a: 's, { if attrs.is_empty() { return; } out.append(' '); for item in attrs.iter().take(attrs.len() - 1) { item.write_out(out); out.append(' '); } attrs.last().unwrap().write_out(out); } impl Script { pub fn to_element_string(&self) -> String { let mut attrs = Vec::new(); if let Some(src) = &self.src { attrs.push(Attr::new("src", Some(Cow::Borrowed(src)))); } if self.is_async { attrs.push(Attr::new("async", None)); } if self.is_module { attrs.push(Attr::new("type", Some("module".into()))); } attrs.push(Attr::new("crossorigin", None)); StringBuilder::build(|out| { out.append(""); }) .unwrap() } } struct NoOutput; impl lol_html::OutputSink for NoOutput { fn handle_chunk(&mut self, _: &[u8]) {} } fn collect_scripts(doc: &str) -> Result, AnyError> { let mut scripts = Vec::new(); let mut rewriter = lol_html::HtmlRewriter::new( lol_html::Settings { element_content_handlers: vec![element!("script[src]", |el| { let is_ignored = el.has_attribute("deno-ignore") || el.has_attribute("vite-ignore"); if is_ignored { return Ok(()); } let typ = el.get_attribute("type"); let (Some("module") | None) = typ.as_deref() else { return Ok(()); }; let src = el.get_attribute("src").unwrap(); let is_async = el.has_attribute("async"); let is_module = matches!(typ.as_deref(), Some("module")); scripts.push(Script { src: Some(src), is_async, is_module, resolved_path: None, }); Ok(()) })], ..lol_html::Settings::new() }, NoOutput, ); rewriter.write(doc.as_bytes())?; rewriter.end()?; Ok(scripts) } #[derive(Debug, Clone)] pub struct HtmlEntrypoint { pub path: PathBuf, pub scripts: Vec