include-tailwind 0.3.0

simple inclusion of tailwind in rust projects
Documentation


/// includes the raw generated tailwind file like `include_str!`,
/// yields an empty file in debug
/// 
/// call `include_tailwind_raw!(always)` to always and include file
/// (also needs to be enabled in the build.rs config to work)
#[macro_export]
macro_rules! include_tailwind_raw {
    (always) => {
        ::core::include_str!(env!("INCLUDE_TAILWIND_PATH"))
    };
    () => {
        {
            #[cfg(not(debug_assertions))] {
                $crate::include_tailwind_raw!(always)
            }
            #[cfg(debug_assertions)] { "/* this is empty in debug */\n" }
        }
    };
}

/// includes the generated tailwind file wrapped that it can directly be used by
/// the supported web frameworks as a response
/// (the `Stylesheet` Type implements the corosponding trait if the feature is enabled)
/// see the `include_tailwind_raw` docs for the possible arguments
/// 
/// ```rust
/// // axum request handler as example
/// async fn style_css() -> include_tailwind::Stylesheet { include_tailwind!() }
/// ```
#[macro_export]
macro_rules! include_tailwind {
    ($($v:tt)*) => { $crate::Stylesheet($crate::include_tailwind_raw!($($v)*)) };
}

/// the wrapping type for the included tailwind source,
/// automaticly works as a response type for the web frameworks, 
/// enabled by the features
#[derive(Debug, Clone, Copy, Hash)]
pub struct Stylesheet(pub &'static str);

impl AsRef<str> for Stylesheet { fn as_ref(&self) -> &str { &self.0 } }

impl Stylesheet {
    /// returns the raw stylesheet `&str`
    pub fn as_str(&self) -> &'static str { &self.0 }
}

#[cfg(feature = "axum")]
mod axum_support {
    use crate::Stylesheet;
    use axum_core::response::{IntoResponse, Response};
    use http::{header, HeaderMap, HeaderValue};

    impl IntoResponse for Stylesheet {
        fn into_response(self) -> Response {
            (
                HeaderMap::from_iter([
                    (header::CONTENT_TYPE, HeaderValue::from_static("text/css; charset=utf-8"))
                ]),
                self.0,
            ).into_response()
        }
    }
}


/// the result of the `load_tailwind!` macro
#[derive(Debug)]
pub enum LoadTailwind {
    /// expands to
    /// ```html
    /// <style>
    ///     /* styles generated by tailwind */
    /// </style>
    /// ```
    Inline {
        css: &'static str
    },
    /// expands to
    /// ```html
    /// <link rel="stylesheet" type="text/css" href="{path}">
    /// ```
    Loaded {
        path: String,
    },
    /// expands to
    /// ```html
    /// <script src="{jit_url}"></script>
    /// <script>
    ///     tailwind.config = {
    ///         ...config
    ///     }
    /// </script>
    /// ```
    Jit {
        config: &'static str,
        jit_url: &'static str,
    },
}

#[cfg(feature = "maud")]
mod maud_support {
    use crate::LoadTailwind;
    use maud::Render;

    impl Render for LoadTailwind {
        #[inline]
        fn render(&self) -> maud::Markup {
            maud::PreEscaped(self.to_string())
        }
    }
}

impl std::fmt::Display for LoadTailwind {
    #[inline]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LoadTailwind::Inline { css } => {
                write!(f, "
<!-- included inline by include-tailwind -->
<style>
{css}
</style>")?;
            },
            LoadTailwind::Loaded { path } => {
                write!(f, "<link rel=\"stylesheet\" type=\"text/css\" href=\"{path}\">")?;
            },
            LoadTailwind::Jit { config, jit_url } => {
                write!(f, "
<script src=\"{jit_url}\"></script>
<script>
{config}
</script>")?;
            },
        }
        Ok(())
    }
}

/// automaticly loads tailwind, depending on the config
/// the macro should be called in the head to the html doc:
///
/// ```rust
/// // example for maud
///
/// html! {
///     (DOCTYPE);
///     html {
///         head {
///             (load_tailwind!());
///         }
///         body {
///             div."w-prose mx-auto" { "Tailwind just works" }
///         }
///     }
/// }
/// ```
///
/// the actual way of loading the file depends on the compile mode (debug_assertions or not)
/// in debug the compilation is skipped as to not slow down the build
///
/// calling `load_tailwind!(jit)` forces jit
///
/// calling `load_tailwind!(always)` has the same behaviour as `include_html!(always)`
/// (never loads using `Jit`)
#[macro_export]
macro_rules! load_tailwind {
    (always) => {
        $crate::LoadTailwind::Inline { css: $crate::include_tailwind_raw!(always) }
    };
    (always, $path:literal) => {
        $crate::LoadTailwind::Loaded { path: String::from($path) }
    };
    (jit) => {
        $crate::LoadTailwind::Jit {
            config: include_str!(env!("INCLUDE_TAILWIND_JIT_CONFIG_PATH")),
            jit_url: env!("INCLUDE_TAILWIND_JIT_URL"),
        }
    };
    () => {
        {
            #[cfg(debug_assertions)] { $crate::load_tailwind!(jit) }
            #[cfg(not(debug_assertions))] { $crate::load_tailwind!(always) }
        }
    };
    ($path:expr) => {
        {
            #[cfg(debug_assertions)] { $crate::load_tailwind!(jit) }
            #[cfg(not(debug_assertions))] { $crate::load_tailwind!(always) }
        }
    };
}