include_tailwind/
lib.rs

1
2
3/// includes the raw generated tailwind file like `include_str!`,
4/// yields an empty file in debug
5/// 
6/// call `include_tailwind_raw!(always)` to always and include file
7/// (also needs to be enabled in the build.rs config to work)
8#[macro_export]
9macro_rules! include_tailwind_raw {
10    (always) => {
11        ::core::include_str!(env!("INCLUDE_TAILWIND_PATH"))
12    };
13    () => {
14        {
15            #[cfg(not(debug_assertions))] {
16                $crate::include_tailwind_raw!(always)
17            }
18            #[cfg(debug_assertions)] { "/* this is empty in debug */\n" }
19        }
20    };
21}
22
23/// includes the generated tailwind file wrapped that it can directly be used by
24/// the supported web frameworks as a response
25/// (the `Stylesheet` Type implements the corosponding trait if the feature is enabled)
26/// see the `include_tailwind_raw` docs for the possible arguments
27/// 
28/// ```rust
29/// // axum request handler as example
30/// async fn style_css() -> include_tailwind::Stylesheet { include_tailwind!() }
31/// ```
32#[macro_export]
33macro_rules! include_tailwind {
34    ($($v:tt)*) => { $crate::Stylesheet($crate::include_tailwind_raw!($($v)*)) };
35}
36
37/// the wrapping type for the included tailwind source,
38/// automaticly works as a response type for the web frameworks, 
39/// enabled by the features
40#[derive(Debug, Clone, Copy, Hash)]
41pub struct Stylesheet(pub &'static str);
42
43impl AsRef<str> for Stylesheet { fn as_ref(&self) -> &str { &self.0 } }
44
45impl Stylesheet {
46    /// returns the raw stylesheet `&str`
47    pub fn as_str(&self) -> &'static str { &self.0 }
48}
49
50#[cfg(feature = "axum")]
51mod axum_support {
52    use crate::Stylesheet;
53    use axum_core::response::{IntoResponse, Response};
54    use http::{header, HeaderMap, HeaderValue};
55
56    impl IntoResponse for Stylesheet {
57        fn into_response(self) -> Response {
58            (
59                HeaderMap::from_iter([
60                    (header::CONTENT_TYPE, HeaderValue::from_static("text/css; charset=utf-8"))
61                ]),
62                self.0,
63            ).into_response()
64        }
65    }
66}
67
68
69/// the result of the `load_tailwind!` macro
70#[derive(Debug)]
71pub enum LoadTailwind {
72    /// expands to
73    /// ```html
74    /// <style>
75    ///     /* styles generated by tailwind */
76    /// </style>
77    /// ```
78    Inline {
79        css: &'static str
80    },
81    /// expands to
82    /// ```html
83    /// <link rel="stylesheet" type="text/css" href="{path}">
84    /// ```
85    Loaded {
86        path: String,
87    },
88    /// expands to
89    /// ```html
90    /// <script src="{jit_url}"></script>
91    /// <script>
92    ///     tailwind.config = {
93    ///         ...config
94    ///     }
95    /// </script>
96    /// ```
97    Jit {
98        config: &'static str,
99        jit_url: &'static str,
100    },
101}
102
103#[cfg(feature = "maud")]
104mod maud_support {
105    use crate::LoadTailwind;
106    use maud::Render;
107
108    impl Render for LoadTailwind {
109        #[inline]
110        fn render(&self) -> maud::Markup {
111            maud::PreEscaped(self.to_string())
112        }
113    }
114}
115
116impl std::fmt::Display for LoadTailwind {
117    #[inline]
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            LoadTailwind::Inline { css } => {
121                write!(f, "
122<!-- included inline by include-tailwind -->
123<style>
124{css}
125</style>")?;
126            },
127            LoadTailwind::Loaded { path } => {
128                write!(f, "<link rel=\"stylesheet\" type=\"text/css\" href=\"{path}\">")?;
129            },
130            LoadTailwind::Jit { config, jit_url } => {
131                write!(f, "
132<script src=\"{jit_url}\"></script>
133<script>
134{config}
135</script>")?;
136            },
137        }
138        Ok(())
139    }
140}
141
142/// automaticly loads tailwind, depending on the config
143/// the macro should be called in the head to the html doc:
144///
145/// ```rust
146/// // example for maud
147///
148/// html! {
149///     (DOCTYPE);
150///     html {
151///         head {
152///             (load_tailwind!());
153///         }
154///         body {
155///             div."w-prose mx-auto" { "Tailwind just works" }
156///         }
157///     }
158/// }
159/// ```
160///
161/// the actual way of loading the file depends on the compile mode (debug_assertions or not)
162/// in debug the compilation is skipped as to not slow down the build
163///
164/// calling `load_tailwind!(jit)` forces jit
165///
166/// calling `load_tailwind!(always)` has the same behaviour as `include_html!(always)`
167/// (never loads using `Jit`)
168#[macro_export]
169macro_rules! load_tailwind {
170    (always) => {
171        $crate::LoadTailwind::Inline { css: $crate::include_tailwind_raw!(always) }
172    };
173    (always, $path:literal) => {
174        $crate::LoadTailwind::Loaded { path: String::from($path) }
175    };
176    (jit) => {
177        $crate::LoadTailwind::Jit {
178            config: include_str!(env!("INCLUDE_TAILWIND_JIT_CONFIG_PATH")),
179            jit_url: env!("INCLUDE_TAILWIND_JIT_URL"),
180        }
181    };
182    () => {
183        {
184            #[cfg(debug_assertions)] { $crate::load_tailwind!(jit) }
185            #[cfg(not(debug_assertions))] { $crate::load_tailwind!(always) }
186        }
187    };
188    ($path:expr) => {
189        {
190            #[cfg(debug_assertions)] { $crate::load_tailwind!(jit) }
191            #[cfg(not(debug_assertions))] { $crate::load_tailwind!(always) }
192        }
193    };
194}
195
196