include_tailwind_build/
lib.rs1use std::{env, path::{Path, PathBuf}, process::Command};
2
3pub use serde_json::json;
4
5impl Default for BuildConfig { fn default() -> Self { Self::new() } }
6#[derive(Debug, Clone)]
13pub struct BuildConfig {
14 css_path: Option<PathBuf>,
15 always: bool,
16 cdn_src: String,
17 tailwind_version: String,
18}
19
20
21impl BuildConfig {
22 pub fn new() -> Self {
24 Self {
25 css_path: None, cdn_src: format!("https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"),
27 tailwind_version: format!("@tailwindcss/cli@4.3.0"),
28 always: false,
29 }
30 }
31
32 pub fn with_tailwind_package(mut self, version: impl Into<String>) -> Self {
36 self.tailwind_version = version.into(); self
37 }
38
39 pub fn with_path(mut self, p: Option<impl AsRef<Path>>) -> Self {
49 self.css_path = p.map(|v| v.as_ref().to_path_buf()); self
50 }
51
52 pub fn with_cdn_src(mut self, s: impl Into<String>) -> Self {
54 self.cdn_src = s.into(); self
55 }
56
57 pub fn always(mut self) -> Self { self.always = true; self }
60
61 fn is_release() -> bool {
62 println!("cargo:rerun-if-env-changed=PROFILE");
63
64 match env::var("PROFILE").as_ref().map(|v| v.as_str()) {
65 Ok("release") => true,
66 Ok("debug") => false,
67 Ok(v) => {
68 println!("cargo:warning='PROFILE' was neither release nor debug ('{v}')");
69 false
70 },
71 Err(_) => {
72 println!("cargo:warning='PROFILE' was not defined, defaulting to debug");
73 false
74 },
75 }
76 }
77
78 const DEFAULT_STYLE_CSS: &'static str = r#"
79@import "tailwindcss";
80@source "{src_dir}/**/*.{rs,html,js}"
81"#;
82
83 fn css_path(&self) -> PathBuf {
84 let p = if let Some(css_path) = &self.css_path {
85 css_path.clone()
86 } else {
87 let default_p = PathBuf::from("style.css");
88 if default_p.exists() { default_p }
89 else {
90 let temp_p = PathBuf::from(std::env::var("OUT_DIR").unwrap())
91 .join("style.css");
92
93 if !temp_p.exists() {
94 std::fs::write(&temp_p, Self::DEFAULT_STYLE_CSS)
95 .expect("could not write temp style.css file");
96 }
97
98 temp_p
99 }
100 };
101 println!("cargo:rerun-if-changed={}", p.to_str().unwrap());
102 p
103 }
104
105 fn write_css_string(&self, out_dir: &Path, src_dir: &Path) -> Result<(), Error> {
106 let css_path = self.css_path();
107 let css_string = std::fs::read_to_string(&css_path)
108 .map_err(|err| Error::StyleCssNotFound(css_path, err))?
109 .replace("{src_dir}", src_dir.to_str().ok_or(Error::InvalidSrcPath)?);
110 std::fs::write(out_dir.join("style.in.css"), css_string)?;
111 Ok(())
112 }
113
114 fn compile_tailwind(&self, out_dir: &Path) -> Result<(), Error> {
115 let tw_in_path = out_dir.join("style.in.css");
116 let tw_out_path = out_dir.join("style.css");
117
118 if !Command::new("npx")
119 .args([&self.tailwind_version])
120 .arg("-i").arg(&tw_in_path)
121 .arg("-o").arg(&tw_out_path)
122 .args(["--minify"])
123 .current_dir(out_dir)
124 .status().unwrap()
125 .success() {
126 panic!("could not build styles");
127 }
128
129 Ok(())
130 }
131
132 fn setup_jit(&self, out_dir: &Path) -> Result<(), Error> {
134 let jit_config_path = out_dir.join("style.in.css");
135
136 println!("cargo:rustc-env=INCLUDE_TAILWIND_JIT_CONFIG_PATH={}",
137 jit_config_path.to_str().unwrap());
138
139 println!("cargo:rustc-env=INCLUDE_TAILWIND_JIT_URL={}", self.cdn_src);
140
141 Ok(())
142 }
143
144 pub fn build(&self) -> Result<(), Error> {
146 let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not provided"));
147 let src_dir = std::fs::canonicalize("./src").expect("could not canonicalize");
148 let release = Self::is_release();
149
150 self.write_css_string(&out_dir, &src_dir)?;
151
152 if release || self.always {
153 self.compile_tailwind(&out_dir)?;
154 } else {
155 self.setup_jit(&out_dir)?;
156 }
157
158 Ok(())
159 }
160}
161
162#[derive(Debug, thiserror::Error)]
163pub enum Error {
164 #[error(transparent)]
165 Io(#[from] std::io::Error),
166 #[error("the source dir contained invalid unicode")]
167 InvalidSrcPath,
168 #[error("could not read style.css at '{0}' -> {1}")]
169 StyleCssNotFound(PathBuf, std::io::Error),
170 #[error("tailwind could not be installed")]
171 TailwindInstallError,
172}
173
174pub fn build_tailwind() -> Result<(), Error> { BuildConfig::default().build() }
176