1#![warn(clippy::pedantic)]
2
3use vite_static_shared::Manifest;
4
5#[allow(unused_imports)]
7use vite_static_shared::ManifestChunk;
8
9#[derive(Clone, Debug, Default)]
10struct HtmlLinks {
11 stylesheets: Vec<&'static str>,
12 scripts: Vec<&'static str>,
13 preloads: Vec<&'static str>,
14}
15
16pub struct HtmlIntegration {
35 manifest: Box<dyn Manifest<'static>>,
36 links: HtmlLinks,
37}
38
39impl HtmlIntegration {
40 #[must_use]
44 pub fn new(manifest: Box<dyn Manifest<'static>>) -> Self {
45 Self {
46 manifest,
47 links: HtmlLinks::default(),
48 }
49 }
50
51 fn import_as(&mut self, chunk: &str, is_dependency: bool) {
53 let chunk = self
54 .manifest
55 .chunk_by_key(chunk)
56 .unwrap_or_else(|| panic!(r#"failed to find chunk "{chunk}""#));
57
58 for css in chunk.css {
59 if !self.links.stylesheets.contains(css) {
60 self.links.stylesheets.push(css);
61 }
62 }
63
64 for import in chunk.imports {
65 let import_file = self
66 .manifest
67 .resolve_output(import)
68 .unwrap_or_else(|| panic!(r#"failed to find imported chunk "{import}""#));
69
70 if !self.links.preloads.contains(&import_file) {
71 self.import_as(import, true);
72 }
73 }
74
75 if is_dependency {
76 self.links.preloads.push(chunk.file);
77 } else {
78 self.links.scripts.push(chunk.file);
79 }
80 }
81
82 #[must_use]
90 pub fn import(mut self, chunk: &str) -> Self {
91 self.import_as(chunk, false);
92 self
93 }
94
95 #[must_use]
97 pub fn build_lines(self) -> Vec<String> {
98 let links = self.links;
99 let mut html = Vec::new();
100
101 for style in links.stylesheets {
102 html.push(format!(r#"<link rel="stylesheet" href="/{style}" />"#));
103 }
104 for script in links.scripts {
105 html.push(format!(
106 r#"<script type="module" src="/{script}"></script>"#
107 ));
108 }
109 for preload in links.preloads {
110 html.push(format!(r#"<link rel="modulepreload" href="/{preload}" />"#));
111 }
112
113 html
114 }
115
116 #[must_use]
118 pub fn build(self) -> String {
119 self.build_lines().join("\n")
120 }
121}
122
123impl Clone for HtmlIntegration {
124 fn clone(&self) -> Self {
125 Self {
126 manifest: self.manifest.boxed(),
127 links: self.links.clone(),
128 }
129 }
130}