#![warn(clippy::pedantic)]
use std::borrow::Cow;
use vite_static_shared::DynManifest;
#[allow(unused_imports)]
use vite_static_shared::{Manifest, ManifestChunk};
pub struct HtmlIntegration<'m> {
manifest: DynManifest<'m>,
stylesheets: Vec<Cow<'m, str>>,
scripts: Vec<Cow<'m, str>>,
modulepreloads: Vec<Cow<'m, str>>,
preloads: Vec<(&'static str, Cow<'m, ManifestChunk<'m>>)>,
}
impl<'m> HtmlIntegration<'m> {
#[must_use]
pub fn new(manifest: DynManifest<'m>) -> Self {
Self {
manifest,
stylesheets: Vec::new(),
scripts: Vec::new(),
modulepreloads: Vec::new(),
preloads: Vec::new(),
}
}
#[must_use]
pub fn import(mut self, chunk: &str) -> Self {
self.import_as(chunk, false);
self
}
#[must_use]
pub fn preload(mut self, as_filetype: &'static str, chunk: &str) -> Self {
let chunk = self
.manifest
.chunk_by_key(chunk)
.unwrap_or_else(|| panic!(r#"failed to find chunk "{chunk}""#));
self.preloads.push((as_filetype, chunk));
self
}
#[must_use]
pub fn stylesheet(mut self, chunk: &str) -> Self {
let chunk = self
.manifest
.chunk_by_key(chunk)
.unwrap_or_else(|| panic!(r#"failed to find chunk "{chunk}""#));
self.stylesheets.push(chunk.file.clone());
self
}
#[must_use]
pub fn build_lines(self) -> Vec<String> {
let mut html = Vec::new();
let base = if self.manifest.base() == "/" {
""
} else {
&self.manifest.base()
};
for (filetype, preload) in self.preloads {
html.push(format!(
r#"<link rel="preload" href="{base}/{path}" as="{filetype}" type="{mimetype}" crossorigin />"#,
path = preload.file,
mimetype = preload.mime_type
));
}
for style in self.stylesheets {
html.push(format!(
r#"<link rel="stylesheet" href="{base}/{style}" />"#
));
}
for script in self.scripts {
html.push(format!(
r#"<script type="module" src="{base}/{script}"></script>"#
));
}
for modulepreload in self.modulepreloads {
html.push(format!(
r#"<link rel="modulepreload" href="{base}/{modulepreload}" />"#
));
}
html
}
#[must_use]
pub fn build(self) -> String {
self.build_lines().join("\n")
}
}
impl HtmlIntegration<'_> {
fn import_as(&mut self, chunk: &str, is_dependency: bool) {
let chunk = self
.manifest
.chunk_by_key(chunk)
.unwrap_or_else(|| panic!(r#"failed to find chunk "{chunk}""#));
for css in chunk.css.as_ref() {
if !self.stylesheets.contains(css) {
self.stylesheets.push(css.clone());
}
}
for import in chunk.imports.as_ref() {
let import_file = self
.manifest
.resolve_output(import)
.unwrap_or_else(|| panic!(r#"failed to find imported chunk "{import}""#));
if !self.modulepreloads.contains(&import_file) {
self.import_as(import, true);
}
}
if is_dependency {
self.modulepreloads.push(chunk.file.clone());
} else {
self.scripts.push(chunk.file.clone());
}
}
}
impl Clone for HtmlIntegration<'_> {
fn clone(&self) -> Self {
Self {
manifest: self.manifest.boxed(),
stylesheets: self.stylesheets.clone(),
scripts: self.scripts.clone(),
modulepreloads: self.modulepreloads.clone(),
preloads: self.preloads.clone(),
}
}
}