diff --git a/src/pipelines/mod.rs b/src/pipelines/mod.rs
index 229dbca1..1ceb872e 100644
--- a/src/pipelines/mod.rs
+++ b/src/pipelines/mod.rs
@@ -12,6 +12,7 @@ mod js;
mod rust;
mod sass;
mod tailwind_css;
+mod tailwind_css_extra;
pub use html::HtmlPipeline;
@@ -28,6 +29,7 @@ use crate::{
rust::{RustApp, RustAppOutput},
sass::{Sass, SassOutput},
tailwind_css::{TailwindCss, TailwindCssOutput},
+ tailwind_css_extra::{TailwindCssExtra, TailwindCssExtraOutput},
},
processing::minify::{minify_css, minify_js},
};
@@ -78,6 +80,7 @@ pub enum TrunkAsset {
Css(Css),
Sass(Sass),
TailwindCss(TailwindCss),
+ TailwindCssExtra(TailwindCssExtra),
Js(Js),
Icon(Icon),
Inline(Inline),
@@ -120,6 +123,9 @@ impl TrunkAsset {
TailwindCss::TYPE_TAILWIND_CSS => {
Self::TailwindCss(TailwindCss::new(cfg, html_dir, attrs, id).await?)
}
+ TailwindCssExtra::TYPE_TAILWIND_CSS_EXTRA => Self::TailwindCssExtra(
+ TailwindCssExtra::new(cfg, html_dir, attrs, id).await?,
+ ),
_ => bail!(
r#"unknown attr value `rel="{}"`; please ensure the value is lowercase and is a supported asset type"#,
rel
@@ -138,6 +144,7 @@ impl TrunkAsset {
Self::Css(inner) => inner.spawn(),
Self::Sass(inner) => inner.spawn(),
Self::TailwindCss(inner) => inner.spawn(),
+ Self::TailwindCssExtra(inner) => inner.spawn(),
Self::Js(inner) => inner.spawn(),
Self::Icon(inner) => inner.spawn(),
Self::Inline(inner) => inner.spawn(),
@@ -153,6 +160,7 @@ pub enum TrunkAssetPipelineOutput {
Css(CssOutput),
Sass(SassOutput),
TailwindCss(TailwindCssOutput),
+ TailwindCssExtra(TailwindCssExtraOutput),
Js(JsOutput),
Icon(IconOutput),
Inline(InlineOutput),
@@ -168,6 +176,7 @@ impl TrunkAssetPipelineOutput {
TrunkAssetPipelineOutput::Css(out) => out.finalize(dom).await,
TrunkAssetPipelineOutput::Sass(out) => out.finalize(dom).await,
TrunkAssetPipelineOutput::TailwindCss(out) => out.finalize(dom).await,
+ TrunkAssetPipelineOutput::TailwindCssExtra(out) => out.finalize(dom).await,
TrunkAssetPipelineOutput::Js(out) => out.finalize(dom).await,
TrunkAssetPipelineOutput::Icon(out) => out.finalize(dom).await,
TrunkAssetPipelineOutput::Inline(out) => out.finalize(dom).await,
diff --git a/src/pipelines/tailwind_css_extra.rs b/src/pipelines/tailwind_css_extra.rs
new file mode 100644
index 00000000..c9bfc1dc
--- /dev/null
+++ b/src/pipelines/tailwind_css_extra.rs
@@ -0,0 +1,210 @@
+//! Tailwind CSS asset pipeline.
+
+use super::{
+ data_target_path, AssetFile, AttrWriter, Attrs, TrunkAssetPipelineOutput, ATTR_CONFIG,
+ ATTR_HREF, ATTR_INLINE, ATTR_NO_MINIFY,
+};
+use crate::{
+ common::{self, dist_relative, html_rewrite::Document, nonce, target_path},
+ config::rt::RtcBuild,
+ processing::integrity::{IntegrityType, OutputDigest},
+ tools::{self, Application},
+};
+use anyhow::{Context, Result};
+use std::{path::PathBuf, sync::Arc};
+use tokio::{fs, task::JoinHandle};
+
+/// A tailwind css asset pipeline.
+pub struct TailwindCssExtra {
+ /// The ID of this pipeline's source HTML element.
+ id: usize,
+ /// Runtime build config.
+ cfg: Arc,
+ /// The asset file being processed.
+ asset: AssetFile,
+ /// If the specified tailwind css file should be inlined.
+ use_inline: bool,
+ /// E.g. `disabled`, `id="..."`
+ attrs: Attrs,
+ /// The required integrity setting
+ integrity: IntegrityType,
+ /// Whether to minify or not
+ no_minify: bool,
+ /// Optional target path inside the dist dir.
+ target_path: Option,
+ /// Optional tailwind config to use.
+ tailwind_config: Option,
+}
+
+impl TailwindCssExtra {
+ pub const TYPE_TAILWIND_CSS_EXTRA: &'static str = "tailwind-css-extra";
+
+ pub async fn new(
+ cfg: Arc,
+ html_dir: Arc,
+ attrs: Attrs,
+ id: usize,
+ ) -> Result {
+ // Build the path to the target asset.
+ let href_attr = attrs.get(ATTR_HREF).context(
+ r#"required attr `href` missing for element"#,
+ )?;
+ let tailwind_config = attrs.get(ATTR_CONFIG).cloned();
+ let mut path = PathBuf::new();
+ path.extend(href_attr.split('/'));
+ let asset = AssetFile::new(&html_dir, path).await?;
+ let use_inline = attrs.contains_key(ATTR_INLINE);
+
+ let integrity = IntegrityType::from_attrs(&attrs, &cfg)?;
+ let no_minify = attrs.contains_key(ATTR_NO_MINIFY);
+ let target_path = data_target_path(&attrs)?;
+
+ Ok(Self {
+ id,
+ cfg,
+ asset,
+ use_inline,
+ integrity,
+ attrs,
+ no_minify,
+ target_path,
+ tailwind_config,
+ })
+ }
+
+ /// Spawn the pipeline for this asset type.
+ #[tracing::instrument(level = "trace", skip(self))]
+ pub fn spawn(self) -> JoinHandle> {
+ tokio::spawn(self.run())
+ }
+
+ /// Run this pipeline.
+ #[tracing::instrument(level = "trace", skip(self))]
+ async fn run(self) -> Result {
+ let version = self.cfg.tools.tailwindcss.as_deref();
+ let tailwind = tools::get(
+ Application::TailwindCssExtra,
+ version,
+ self.cfg.offline,
+ &self.cfg.client_options(),
+ )
+ .await?;
+
+ // Compile the target tailwind css file.
+ let path_str = dunce::simplified(&self.asset.path).display().to_string();
+ let file_name = format!("{}.css", &self.asset.file_stem.to_string_lossy());
+ let file_path = dunce::simplified(&self.cfg.staging_dist.join(&file_name))
+ .display()
+ .to_string();
+
+ let mut args = vec!["--input", &path_str, "--output", &file_path];
+
+ if let Some(tailwind_config) = self.tailwind_config.as_ref() {
+ args.push("--config");
+ args.push(tailwind_config);
+ }
+
+ if self.cfg.minify_asset(self.no_minify) {
+ args.push("--minify");
+ }
+
+ let rel_path = common::strip_prefix(&self.asset.path);
+ tracing::debug!(path = ?rel_path, "compiling tailwind css");
+
+ common::run_command(
+ Application::TailwindCssExtra.name(),
+ &tailwind,
+ &args,
+ &self.cfg.core.working_directory,
+ )
+ .await?;
+
+ let css = fs::read_to_string(&file_path).await?;
+ fs::remove_file(&file_path).await?;
+
+ // Check if the specified tailwind css file should be inlined.
+ let css_ref = if self.use_inline {
+ // Avoid writing any files, return the CSS as a String.
+ CssExtraRef::Inline(css)
+ } else {
+ // Hash the contents to generate a file name, and then write the contents to the dist
+ // dir.
+ let hash = seahash::hash(css.as_bytes());
+ let file_name = self
+ .cfg
+ .filehash
+ .then(|| format!("{}-{:x}.css", &self.asset.file_stem.to_string_lossy(), hash))
+ .unwrap_or(file_name);
+
+ let result_dir =
+ target_path(&self.cfg.staging_dist, self.target_path.as_deref(), None).await?;
+ let file_path = result_dir.join(&file_name);
+ let file_href = dist_relative(&self.cfg.staging_dist, &file_path)?;
+
+ let integrity = OutputDigest::generate_from(self.integrity, css.as_bytes());
+
+ // Write the generated CSS to the filesystem.
+ fs::write(&file_path, css)
+ .await
+ .context("error writing tailwind css pipeline output")?;
+
+ // Generate a hashed reference to the new CSS file.
+ CssExtraRef::File(file_href, integrity)
+ };
+
+ tracing::debug!(path = ?rel_path, "finished compiling tailwind css");
+ Ok(TrunkAssetPipelineOutput::TailwindCssExtra(
+ TailwindCssExtraOutput {
+ cfg: self.cfg.clone(),
+ id: self.id,
+ css_ref,
+ attrs: self.attrs,
+ },
+ ))
+ }
+}
+
+/// The output of a Tailwind CSS build pipeline.
+pub struct TailwindCssExtraOutput {
+ /// The runtime build config.
+ pub cfg: Arc,
+ /// The ID of this pipeline.
+ pub id: usize,
+ /// Data on the finalized output file.
+ pub css_ref: CssExtraRef,
+ /// The other attributes copied over from the original.
+ pub attrs: Attrs,
+}
+
+/// The resulting CSS of the Tailwind CSS compilation.
+pub enum CssExtraRef {
+ /// CSS to be inlined (for `data-inline`).
+ Inline(String),
+ /// A hashed file reference to a CSS file (default).
+ File(String, OutputDigest),
+}
+
+impl TailwindCssExtraOutput {
+ pub async fn finalize(self, dom: &mut Document) -> Result<()> {
+ let html = match self.css_ref {
+ // Insert the inlined CSS into a `"#,
+ nonce(),
+ attrs = AttrWriter::new(&self.attrs, AttrWriter::EXCLUDE_CSS_INLINE)
+ ),
+ // Link to the CSS file.
+ CssExtraRef::File(file, integrity) => {
+ let mut attrs = self.attrs.clone();
+ integrity.insert_into(&mut attrs);
+
+ format!(
+ r#""#,
+ base = &self.cfg.public_url,
+ attrs = AttrWriter::new(&attrs, AttrWriter::EXCLUDE_CSS_LINK)
+ )
+ }
+ };
+ dom.replace_with_html(&super::trunk_id_selector(self.id), &html)
+ }
+}
diff --git a/src/tools.rs b/src/tools.rs
index 73ed5f31..fb560ec4 100644
--- a/src/tools.rs
+++ b/src/tools.rs
@@ -21,6 +21,8 @@ pub enum Application {
Sass,
/// tailwindcss for generating css
TailwindCss,
+ /// tailwindcss-extra for generating css with DaisyUI bundled.
+ TailwindCssExtra,
/// wasm-bindgen for generating the JS bindings.
WasmBindgen,
/// wasm-opt to improve performance and size of the output file further.
@@ -48,6 +50,7 @@ impl Application {
match self {
Self::Sass => "sass",
Self::TailwindCss => "tailwindcss",
+ Self::TailwindCssExtra => "tailwindcss-extra",
Self::WasmBindgen => "wasm-bindgen",
Self::WasmOpt => "wasm-opt",
}
@@ -59,6 +62,7 @@ impl Application {
match self {
Self::Sass => "sass.bat",
Self::TailwindCss => "tailwindcss.exe",
+ Self::TailwindCssExtra => "tailwindcss-extra.exe",
Self::WasmBindgen => "wasm-bindgen.exe",
Self::WasmOpt => "bin/wasm-opt.exe",
}
@@ -66,6 +70,7 @@ impl Application {
match self {
Self::Sass => "sass",
Self::TailwindCss => "tailwindcss",
+ Self::TailwindCssExtra => "tailwindcss-extra",
Self::WasmBindgen => "wasm-bindgen",
Self::WasmOpt => "bin/wasm-opt",
}
@@ -83,6 +88,7 @@ impl Application {
}
}
Self::TailwindCss => &[],
+ Self::TailwindCssExtra => &[],
Self::WasmBindgen => &[],
Self::WasmOpt => {
if cfg!(target_os = "macos") {
@@ -99,6 +105,7 @@ impl Application {
match self {
Self::Sass => "1.69.5",
Self::TailwindCss => "3.3.5",
+ Self::TailwindCssExtra => "1.7.25",
Self::WasmBindgen => "0.2.89",
Self::WasmOpt => "version_116",
}
@@ -139,6 +146,13 @@ impl Application {
_ => bail!("Unable to download tailwindcss for {target_os} {target_arch}")
},
+ Self::TailwindCssExtra => match (target_os, target_arch) {
+ ("windows", "x86_64") => format!("https://github.com/dobicinaitis/tailwind-cli-extra/releases/download/v{version}/tailwindcss-extra-windows-x64.exe"),
+ ("macos" | "linux", "x86_64") => format!("https://github.com/dobicinaitis/tailwind-cli-extra/releases/download/v{version}/tailwindcss-extra-{target_os}-x64"),
+ ("macos" | "linux", "aarch64") => format!("https://github.com/dobicinaitis/tailwind-cli-extra/releases/download/v{version}/tailwindcss-extra-{target_os}-arm64"),
+ _ => bail!("Unable to download tailwindcss for {target_os} {target_arch}")
+ },
+
Self::WasmBindgen => match (target_os, target_arch) {
("windows", "x86_64") => format!("https://github.com/rustwasm/wasm-bindgen/releases/download/{version}/wasm-bindgen-{version}-x86_64-pc-windows-msvc.tar.gz"),
("macos", "x86_64") => format!("https://github.com/rustwasm/wasm-bindgen/releases/download/{version}/wasm-bindgen-{version}-x86_64-apple-darwin.tar.gz"),
@@ -160,6 +174,7 @@ impl Application {
match self {
Application::Sass => "--version",
Application::TailwindCss => "--help",
+ Application::TailwindCssExtra => "--help",
Application::WasmBindgen => "--version",
Application::WasmOpt => "--version",
}
@@ -180,6 +195,12 @@ impl Application {
.and_then(|s| s.split(" v").nth(1))
.with_context(|| format!("missing or malformed version output: {}", text))?
.to_owned(),
+ Application::TailwindCssExtra => text
+ .lines()
+ .find(|s| !str::is_empty(s))
+ .and_then(|s| s.split(" v").nth(1))
+ .with_context(|| format!("missing or malformed version output: {}", text))?
+ .to_owned(),
Application::WasmBindgen => text
.split(' ')
.nth(1)
@@ -791,4 +812,10 @@ mod tests {
"tailwindcss v3.3.2",
"3.3.2"
);
+ table_test_format_version!(
+ tailwindcss_extra_pre_compiled,
+ Application::TailwindCssExtra,
+ "tailwindcss-extra v1.7.25",
+ "1.7.25"
+ );
}