From 317fe5b17260d51b2368257babaed278626018af Mon Sep 17 00:00:00 2001 From: opensourcecheemsburgers Date: Wed, 18 Dec 2024 01:39:33 +0200 Subject: [PATCH 1/2] Add tailwindcss-extra for bundled DaisyUI to avoid Node dependency. --- src/pipelines/mod.rs | 9 ++ src/pipelines/tailwind_css_extra.rs | 210 ++++++++++++++++++++++++++++ src/tools.rs | 27 ++++ 3 files changed, 246 insertions(+) create mode 100644 src/pipelines/tailwind_css_extra.rs 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..c34b888d 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", } @@ -138,6 +145,13 @@ impl Application { ("macos" | "linux", "aarch64") => format!("https://github.com/tailwindlabs/tailwindcss/releases/download/v{version}/tailwindcss-{target_os}-arm64"), _ => 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"), @@ -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" + ); } From ccc479eb170ea023e66914c6ed93c88b472fadb6 Mon Sep 17 00:00:00 2001 From: opensourcecheemsburgers Date: Sat, 21 Dec 2024 22:04:34 +0200 Subject: [PATCH 2/2] Remove whitespace --- src/tools.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools.rs b/src/tools.rs index c34b888d..fb560ec4 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -145,7 +145,7 @@ impl Application { ("macos" | "linux", "aarch64") => format!("https://github.com/tailwindlabs/tailwindcss/releases/download/v{version}/tailwindcss-{target_os}-arm64"), _ => 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"),